From 67327384e1c0b36a8ae2a2633f2ce3b7b7cf39de Mon Sep 17 00:00:00 2001 From: Maksim Sukharev Date: Tue, 21 Jul 2026 13:00:55 +0200 Subject: [PATCH] fix(files_sharing): skip unresolvable share recipients Assisted-by: ClaudeCode:claude-opus-4-8 Signed-off-by: Maksim Sukharev --- .../lib/Listener/SharesUpdatedListener.php | 10 ++++++- .../tests/SharesUpdatedListenerTest.php | 28 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/apps/files_sharing/lib/Listener/SharesUpdatedListener.php b/apps/files_sharing/lib/Listener/SharesUpdatedListener.php index 800e223c8e9b3..fb5b1c19a4de4 100644 --- a/apps/files_sharing/lib/Listener/SharesUpdatedListener.php +++ b/apps/files_sharing/lib/Listener/SharesUpdatedListener.php @@ -8,6 +8,7 @@ namespace OCA\Files_Sharing\Listener; +use OC\User\NoUserException; use OCA\Files_Sharing\AppInfo\Application; use OCA\Files_Sharing\Config\ConfigLexicon; use OCA\Files_Sharing\Event\UserShareAccessUpdatedEvent; @@ -135,7 +136,14 @@ private function markOrRun(IUser $user, callable $callback): void { $elapsed = $now - $this->firstRun; if ($this->cutOffMarkTime === -1.0 || $elapsed < $this->cutOffMarkTime) { - $callback(); + try { + $callback(); + } catch (NoUserException $e) { + // A share recipient may reference a user id that no backend can resolve anymore + // (e.g. with LazyUser::getUID()) - like remnant / incorrectly removed user. + // Skip this recipient instead of aborting the share operation. + $this->logger->debug('Skipping share mount update for unresolvable user ' . $user->getUID(), ['exception' => $e]); + } } else { $this->markUserForRefresh($user); } diff --git a/apps/files_sharing/tests/SharesUpdatedListenerTest.php b/apps/files_sharing/tests/SharesUpdatedListenerTest.php index 74347e3382d69..3fa913ea62cb6 100644 --- a/apps/files_sharing/tests/SharesUpdatedListenerTest.php +++ b/apps/files_sharing/tests/SharesUpdatedListenerTest.php @@ -7,6 +7,7 @@ namespace OCA\Files_Sharing\Tests; +use OC\User\NoUserException; use OCA\Files_Sharing\Config\ConfigLexicon; use OCA\Files_Sharing\Event\UserShareAccessUpdatedEvent; use OCA\Files_Sharing\Listener\SharesUpdatedListener; @@ -117,6 +118,33 @@ public function testShareAddedFilterOwner() { $this->sharesUpdatedListener->handle($event); } + public function testShareAddedSkipsUnresolvableUser(): void { + $share = $this->createMock(IShare::class); + $user1 = $this->createUser('user1', ''); + $user2 = $this->createUser('user2', ''); + + $this->manager->method('getUsersForShare') + ->willReturn([$user1, $user2]); + + $event = new ShareCreatedEvent($share); + + // user1 is an orphaned recipient that no backend can resolve + $this->shareRecipientUpdater + ->expects($this->exactly(2)) + ->method('updateForAddedShare') + ->willReturnCallback(function (IUser $user) use ($user1): void { + if ($user === $user1) { + throw new NoUserException('Backends provided no user object'); + } + }); + + // the failure is logged, not thrown + $this->logger->expects($this->once())->method('debug'); + + // must not throw: user2 is still processed + $this->sharesUpdatedListener->handle($event); + } + public function testShareAccessUpdated() { $user1 = $this->createUser('user1', ''); $user2 = $this->createUser('user2', '');