From 8cecf7a26d212bf7bc2019770c783670af0bd6dd Mon Sep 17 00:00:00 2001 From: Jos Poortvliet Date: Sat, 29 Aug 2026 22:26:36 +0200 Subject: [PATCH] fix(search): search as the user the provider is given MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SearchTablesProvider receives the account to search as, but called TableService::search() and ViewService::search() without it. Both accept an optional $userId and fall back to PermissionsService::preCheckUserId(null), which resolves the session user — so any caller searching on behalf of someone else silently received its own results instead. This has no effect on unified search, where the two are always the same person. It matters for callers that search on behalf of another account, such as background jobs or compliance tooling, where returning the caller's own tables is not a degraded result but a wrong one. Both services already take the parameter, so this passes the argument that already exists rather than changing any API. Assisted-by: ClaudeCode:claude-opus-5 Signed-off-by: Jos Poortvliet --- lib/Search/SearchTablesProvider.php | 4 +- .../unit/Search/SearchTablesProviderTest.php | 100 ++++++++++++++++++ 2 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 tests/unit/Search/SearchTablesProviderTest.php diff --git a/lib/Search/SearchTablesProvider.php b/lib/Search/SearchTablesProvider.php index 97c51a47d0..d2f4404cdc 100644 --- a/lib/Search/SearchTablesProvider.php +++ b/lib/Search/SearchTablesProvider.php @@ -79,7 +79,7 @@ public function search(IUser $user, ISearchQuery $query): SearchResult { ); // look for tables - $tables = $this->tableService->search($term, $limit, $offset); + $tables = $this->tableService->search($term, $limit, $offset, $user->getUID()); $formattedTablesResults = array_map(fn (Table $table): SearchResultEntry => new SearchResultEntry( $appIconUrl, $table->getEmoji() . ' ' . $table->getTitle(), @@ -90,7 +90,7 @@ public function search(IUser $user, ISearchQuery $query): SearchResult { ), $tables); // look for views - $views = $this->viewService->search($term, $limit, $offset); + $views = $this->viewService->search($term, $limit, $offset, $user->getUID()); $formattedViewResults = array_map(fn (View $view): SearchResultEntry => new SearchResultEntry( $viewIconUrl, $view->getEmoji() . ' ' . $view->getTitle(), diff --git a/tests/unit/Search/SearchTablesProviderTest.php b/tests/unit/Search/SearchTablesProviderTest.php new file mode 100644 index 0000000000..35c5ece695 --- /dev/null +++ b/tests/unit/Search/SearchTablesProviderTest.php @@ -0,0 +1,100 @@ +appManager = $this->createMock(IAppManager::class); + $this->tableService = $this->createMock(TableService::class); + $this->viewService = $this->createMock(ViewService::class); + + $l10n = $this->createMock(IL10N::class); + $l10n->method('t')->willReturnArgument(0); + $l10n->method('n')->willReturnArgument(0); + + $urlGenerator = $this->createMock(IURLGenerator::class); + $urlGenerator->method('imagePath')->willReturn(''); + $urlGenerator->method('getAbsoluteURL')->willReturn(''); + $urlGenerator->method('linkToRoute')->willReturn(''); + + $this->provider = new SearchTablesProvider( + $this->appManager, + $l10n, + $this->viewService, + $this->tableService, + $urlGenerator, + ); + } + + private function query(): MockObject { + $query = $this->createMock(ISearchQuery::class); + $query->method('getLimit')->willReturn(10); + $query->method('getTerm')->willReturn('budget'); + $query->method('getCursor')->willReturn(null); + + return $query; + } + + private function user(string $uid): MockObject { + $user = $this->createMock(IUser::class); + $user->method('getUID')->willReturn($uid); + + return $user; + } + + /** + * The provider is handed the user to search as, and both services accept one. Without passing + * it they fall back to the session user, so a caller searching on behalf of someone else + * silently gets its own results. + */ + public function testSearchesAsTheGivenUser(): void { + $this->appManager->method('isEnabledForUser')->willReturn(true); + + $this->tableService->expects($this->once()) + ->method('search') + ->with('budget', 10, 0, 'alice') + ->willReturn([]); + + $this->viewService->expects($this->once()) + ->method('search') + ->with('budget', 10, 0, 'alice') + ->willReturn([]); + + $this->provider->search($this->user('alice'), $this->query()); + } + + public function testReturnsNothingWhenTheAppIsDisabledForTheUser(): void { + $this->appManager->method('isEnabledForUser')->willReturn(false); + + $this->tableService->expects($this->never())->method('search'); + $this->viewService->expects($this->never())->method('search'); + + $result = $this->provider->search($this->user('alice'), $this->query()); + + $this->assertSame([], $result->jsonSerialize()['entries']); + } +}