Skip to content

Commit 970c899

Browse files
Merge pull request #63896 from nextcloud/backport/63883/stable35
[stable35] Improve user enumeration
2 parents 5d6dc42 + 6932016 commit 970c899

24 files changed

Lines changed: 237 additions & 111 deletions

File tree

apps/files_sharing/lib/Controller/ShareesAPIController.php

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ public function __construct(
7474
protected IManager $shareManager,
7575
protected ISearch $collaboratorSearch,
7676
protected FederatedShareProvider $federatedShareProvider,
77+
protected IAppManager $appManager,
7778
) {
7879
parent::__construct($appName, $request);
7980
}
@@ -161,7 +162,7 @@ public function search(string $search = '', ?string $itemType = null, int $page
161162
}
162163

163164
// FIXME: DI
164-
if (Server::get(IAppManager::class)->isEnabledForUser('circles') && class_exists('\OCA\Circles\ShareByCircleProvider')) {
165+
if ($this->appManager->isEnabledForUser('circles') && class_exists('\OCA\Circles\ShareByCircleProvider')) {
165166
$shareTypes[] = IShare::TYPE_CIRCLE;
166167
}
167168

@@ -183,25 +184,26 @@ public function search(string $search = '', ?string $itemType = null, int $page
183184
$this->result['lookupEnabled'] = Server::get(GlobalScaleIConfig::class)->isGlobalScaleEnabled();
184185
// TODO: Reconsider using lookup server for non-global-scale federation
185186

186-
[$result, $hasMoreResults] = $this->collaboratorSearch->search($search, $shareTypes, $this->result['lookupEnabled'], $this->limit, $this->offset);
187+
[$result, $hasMoreResults] = $this->collaboratorSearch->filteredSearch($search, $shareTypes, $this->result['lookupEnabled'], $itemType, null, $this->limit, $this->offset);
187188

188189
// extra treatment for 'exact' subarray, with a single merge expected keys might be lost
189190
if (isset($result['exact'])) {
190191
$result['exact'] = array_merge($this->result['exact'], $result['exact']);
191192
}
192-
$this->result = array_merge($this->result, $result);
193-
$response = new DataResponse($this->result);
193+
/** @var Files_SharingShareesSearchResult $result */
194+
$result = array_merge($this->result, $result);
194195

196+
$headers = [];
195197
if ($hasMoreResults) {
196-
$response->setHeaders(['Link' => $this->getPaginationLink($page, [
198+
$headers['Link'] = $this->getPaginationLink($page, [
197199
'search' => $search,
198200
'itemType' => $itemType,
199201
'shareType' => $shareTypes,
200202
'perPage' => $perPage,
201-
])]);
203+
]);
202204
}
203205

204-
return $response;
206+
return new DataResponse($result, Http::STATUS_OK, $headers);
205207
}
206208

207209
/**

apps/files_sharing/tests/Controller/ShareesAPIControllerTest.php

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use OCA\FederatedFileSharing\FederatedShareProvider;
1212
use OCA\Files_Sharing\Controller\ShareesAPIController;
1313
use OCA\Files_Sharing\Tests\TestCase;
14+
use OCP\App\IAppManager;
1415
use OCP\AppFramework\Http\DataResponse;
1516
use OCP\AppFramework\OCS\OCSBadRequestException;
1617
use OCP\Collaboration\Collaborators\ISearch;
@@ -30,26 +31,14 @@
3031
*/
3132
#[\PHPUnit\Framework\Attributes\Group(name: 'DB')]
3233
class ShareesAPIControllerTest extends TestCase {
33-
/** @var ShareesAPIController */
34-
protected $sharees;
35-
36-
/** @var string */
37-
protected $uid;
38-
39-
/** @var IRequest|MockObject */
40-
protected $request;
41-
42-
/** @var IManager|MockObject */
34+
protected ShareesAPIController $sharees;
35+
protected string $uid;
36+
protected IRequest&MockObject $request;
4337
protected $shareManager;
44-
45-
/** @var ISearch|MockObject */
46-
protected $collaboratorSearch;
47-
48-
/** @var IConfig|MockObject */
49-
protected $config;
50-
51-
/** @var FederatedShareProvider|MockObject */
52-
protected $federatedShareProvider;
38+
protected ISearch&MockObject $collaboratorSearch;
39+
protected IConfig&MockObject $config;
40+
protected FederatedShareProvider&MockObject $federatedShareProvider;
41+
protected IAppManager&MockObject $appManager;
5342

5443
protected function setUp(): void {
5544
parent::setUp();
@@ -59,11 +48,11 @@ protected function setUp(): void {
5948
$this->shareManager = $this->createMock(IManager::class);
6049
$this->config = $this->createMock(IConfig::class);
6150

62-
/** @var IURLGenerator|MockObject $urlGeneratorMock */
6351
$urlGeneratorMock = $this->createMock(IURLGenerator::class);
6452

6553
$this->collaboratorSearch = $this->createMock(ISearch::class);
6654
$this->federatedShareProvider = $this->createMock(FederatedShareProvider::class);
55+
$this->appManager = $this->createMock(IAppManager::class);
6756

6857
$this->sharees = new ShareesAPIController(
6958
'files_sharing',
@@ -73,7 +62,8 @@ protected function setUp(): void {
7362
$urlGeneratorMock,
7463
$this->shareManager,
7564
$this->collaboratorSearch,
76-
$this->federatedShareProvider
65+
$this->federatedShareProvider,
66+
$this->appManager,
7767
);
7868
}
7969

@@ -267,7 +257,8 @@ public function testSearch(
267257
$urlGenerator,
268258
$this->shareManager,
269259
$this->collaboratorSearch,
270-
$this->federatedShareProvider
260+
$this->federatedShareProvider,
261+
$this->appManager,
271262
])
272263
->onlyMethods(['isRemoteSharingAllowed', 'isRemoteGroupSharingAllowed'])
273264
->getMock();
@@ -276,8 +267,8 @@ public function testSearch(
276267
sort($expectedShareTypes);
277268

278269
$this->collaboratorSearch->expects($this->once())
279-
->method('search')
280-
->with($search, $expectedShareTypes, $this->anything(), $perPage, $perPage * ($page - 1))
270+
->method('filteredSearch')
271+
->with($search, $expectedShareTypes, $this->anything(), $itemType, null, $perPage, $perPage * ($page - 1))
281272
->willReturn([[], false]);
282273

283274
$sharees->expects($this->any())
@@ -364,15 +355,16 @@ public function testSearchInvalid($getData, $message): void {
364355
$urlGenerator,
365356
$this->shareManager,
366357
$this->collaboratorSearch,
367-
$this->federatedShareProvider
358+
$this->federatedShareProvider,
359+
$this->appManager,
368360
])
369361
->onlyMethods(['isRemoteSharingAllowed'])
370362
->getMock();
371363
$sharees->expects($this->never())
372364
->method('isRemoteSharingAllowed');
373365

374366
$this->collaboratorSearch->expects($this->never())
375-
->method('search');
367+
->method('filteredSearch');
376368

377369
try {
378370
$sharees->search('', null, $page, $perPage, null);

apps/user_status/lib/Controller/StatusesController.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
use OCP\AppFramework\Http\DataResponse;
2020
use OCP\AppFramework\OCS\OCSNotFoundException;
2121
use OCP\AppFramework\OCSController;
22+
use OCP\EventDispatcher\IEventDispatcher;
2223
use OCP\IRequest;
24+
use OCP\User\Events\UserEnumerationFilterEvent;
2325
use OCP\UserStatus\IUserStatus;
2426

2527
/**
@@ -38,7 +40,8 @@ class StatusesController extends OCSController {
3840
public function __construct(
3941
string $appName,
4042
IRequest $request,
41-
private StatusService $service,
43+
private readonly StatusService $service,
44+
private readonly IEventDispatcher $eventDispatcher,
4245
) {
4346
parent::__construct($appName, $request);
4447
}
@@ -57,6 +60,15 @@ public function __construct(
5760
public function findAll(?int $limit = null, ?int $offset = null): DataResponse {
5861
$allStatuses = $this->service->findAll($limit, $offset);
5962

63+
$users = array_map(fn (UserStatus $userStatus): string => $userStatus->getUserId(), $allStatuses);
64+
$event = new UserEnumerationFilterEvent($users);
65+
$this->eventDispatcher->dispatchTyped($event);
66+
67+
if ($users !== $event->getUsers()) {
68+
$removedUsers = $event->getFilteredOutUsers();
69+
$allStatuses = array_filter($allStatuses, fn (UserStatus $userStatus): bool => !in_array($userStatus->getUserId(), $removedUsers, true));
70+
}
71+
6072
return new DataResponse(array_values(array_map(function ($userStatus) {
6173
return $this->formatStatus($userStatus);
6274
}, $allStatuses)));

apps/user_status/lib/Service/StatusService.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public function __construct(
8282
/**
8383
* @param int|null $limit
8484
* @param int|null $offset
85-
* @return UserStatus[]
85+
* @return list<UserStatus>
8686
*/
8787
public function findAll(?int $limit = null, ?int $offset = null): array {
8888
// Return empty array if user enumeration is disabled or limited to groups
@@ -92,9 +92,9 @@ public function findAll(?int $limit = null, ?int $offset = null): array {
9292
return [];
9393
}
9494

95-
return array_map(function ($status) {
95+
return array_values(array_map(function ($status) {
9696
return $this->processStatus($status);
97-
}, $this->mapper->findAll($limit, $offset));
97+
}, $this->mapper->findAll($limit, $offset)));
9898
}
9999

100100
/**

apps/user_status/tests/Unit/Controller/StatusesControllerTest.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,24 @@
1414
use OCA\UserStatus\Service\StatusService;
1515
use OCP\AppFramework\Db\DoesNotExistException;
1616
use OCP\AppFramework\OCS\OCSNotFoundException;
17+
use OCP\EventDispatcher\IEventDispatcher;
1718
use OCP\IRequest;
1819
use PHPUnit\Framework\MockObject\MockObject;
1920
use Test\TestCase;
2021

2122
class StatusesControllerTest extends TestCase {
2223
private StatusService&MockObject $service;
24+
private IEventDispatcher&MockObject $eventDispatcher;
2325
private StatusesController $controller;
2426

2527
protected function setUp(): void {
2628
parent::setUp();
2729

2830
$request = $this->createMock(IRequest::class);
2931
$this->service = $this->createMock(StatusService::class);
32+
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
3033

31-
$this->controller = new StatusesController('user_status', $request, $this->service);
34+
$this->controller = new StatusesController('user_status', $request, $this->service, $this->eventDispatcher);
3235
}
3336

3437
public function testFindAll(): void {

core/Controller/AutoCompleteController.php

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,8 @@
1515
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
1616
use OCP\AppFramework\Http\DataResponse;
1717
use OCP\AppFramework\OCSController;
18-
use OCP\Collaboration\AutoComplete\AutoCompleteFilterEvent;
1918
use OCP\Collaboration\AutoComplete\IManager;
2019
use OCP\Collaboration\Collaborators\ISearch;
21-
use OCP\EventDispatcher\IEventDispatcher;
2220
use OCP\IRequest;
2321
use OCP\Share\IShare;
2422

@@ -31,7 +29,6 @@ public function __construct(
3129
IRequest $request,
3230
private ISearch $collaboratorSearch,
3331
private IManager $autoCompleteManager,
34-
private IEventDispatcher $dispatcher,
3532
) {
3633
parent::__construct($appName, $request);
3734
}
@@ -55,19 +52,7 @@ public function __construct(
5552
public function get(string $search, ?string $itemType, ?string $itemId, ?string $sorter = null, array $shareTypes = [IShare::TYPE_USER], int $limit = 10): DataResponse {
5653
// if enumeration/user listings are disabled, we'll receive an empty
5754
// result from search() – thus nothing else to do here.
58-
[$results,] = $this->collaboratorSearch->search($search, $shareTypes, false, $limit, 0);
59-
60-
$event = new AutoCompleteFilterEvent(
61-
$results,
62-
$search,
63-
$itemType,
64-
$itemId,
65-
$sorter,
66-
$shareTypes,
67-
$limit,
68-
);
69-
$this->dispatcher->dispatchTyped($event);
70-
$results = $event->getResults();
55+
[$results,] = $this->collaboratorSearch->filteredSearch($search, $shareTypes, false, $itemType, $itemId, $limit, 0);
7156

7257
$exactMatches = $results['exact'];
7358
unset($results['exact']);

lib/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1145,6 +1145,7 @@
11451145
'OCP\\User\\Events\\UserConfigChangedEvent' => $baseDir . '/lib/public/User/Events/UserConfigChangedEvent.php',
11461146
'OCP\\User\\Events\\UserCreatedEvent' => $baseDir . '/lib/public/User/Events/UserCreatedEvent.php',
11471147
'OCP\\User\\Events\\UserDeletedEvent' => $baseDir . '/lib/public/User/Events/UserDeletedEvent.php',
1148+
'OCP\\User\\Events\\UserEnumerationFilterEvent' => $baseDir . '/lib/public/User/Events/UserEnumerationFilterEvent.php',
11481149
'OCP\\User\\Events\\UserFirstTimeLoggedInEvent' => $baseDir . '/lib/public/User/Events/UserFirstTimeLoggedInEvent.php',
11491150
'OCP\\User\\Events\\UserIdAssignedEvent' => $baseDir . '/lib/public/User/Events/UserIdAssignedEvent.php',
11501151
'OCP\\User\\Events\\UserIdUnassignedEvent' => $baseDir . '/lib/public/User/Events/UserIdUnassignedEvent.php',

lib/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1186,6 +1186,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
11861186
'OCP\\User\\Events\\UserConfigChangedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/UserConfigChangedEvent.php',
11871187
'OCP\\User\\Events\\UserCreatedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/UserCreatedEvent.php',
11881188
'OCP\\User\\Events\\UserDeletedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/UserDeletedEvent.php',
1189+
'OCP\\User\\Events\\UserEnumerationFilterEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/UserEnumerationFilterEvent.php',
11891190
'OCP\\User\\Events\\UserFirstTimeLoggedInEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/UserFirstTimeLoggedInEvent.php',
11901191
'OCP\\User\\Events\\UserIdAssignedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/UserIdAssignedEvent.php',
11911192
'OCP\\User\\Events\\UserIdUnassignedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/UserIdUnassignedEvent.php',

lib/private/Collaboration/Collaborators/GroupPlugin.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public function __construct(
4242
}
4343

4444
#[\Override]
45-
public function search($search, $limit, $offset, ISearchResult $searchResult): bool {
45+
public function search(string $search, int $limit, int $offset, ISearchResult $searchResult): bool {
4646
if ($this->groupSharingDisabled) {
4747
return false;
4848
}

lib/private/Collaboration/Collaborators/LookupPlugin.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public function __construct(
3434
}
3535

3636
#[\Override]
37-
public function search($search, $limit, $offset, ISearchResult $searchResult): bool {
37+
public function search(string $search, int $limit, int $offset, ISearchResult $searchResult): bool {
3838
$isGlobalScaleEnabled = $this->globalScaleConfig->isGlobalScaleEnabled();
3939
$isLookupServerEnabled = $this->config->getAppValue('files_sharing', 'lookupServerEnabled', 'no') === 'yes';
4040
$hasInternetConnection = $this->config->getSystemValueBool('has_internet_connection', true);

0 commit comments

Comments
 (0)