diff --git a/apps/files_sharing/lib/Listener/SharesUpdatedListener.php b/apps/files_sharing/lib/Listener/SharesUpdatedListener.php index 800e223c8e9b3..a20d70aec53bd 100644 --- a/apps/files_sharing/lib/Listener/SharesUpdatedListener.php +++ b/apps/files_sharing/lib/Listener/SharesUpdatedListener.php @@ -26,6 +26,7 @@ use OCP\Share\Events\ShareMovedEvent; use OCP\Share\Events\ShareTransferredEvent; use OCP\Share\IManager; +use OCP\User\Exceptions\UserNotFoundException; use Psr\Clock\ClockInterface; use Psr\Log\LoggerInterface; @@ -135,7 +136,17 @@ private function markOrRun(IUser $user, callable $callback): void { $elapsed = $now - $this->firstRun; if ($this->cutOffMarkTime === -1.0 || $elapsed < $this->cutOffMarkTime) { - $callback(); + try { + $callback(); + } catch (UserNotFoundException $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->warning( + 'Skipping share mount update for unresolvable user ' . $user->getUID(), + ['app' => Application::APP_ID, 'exception' => $e], + ); + } } else { $this->markUserForRefresh($user); } diff --git a/apps/files_sharing/tests/SharesUpdatedListenerTest.php b/apps/files_sharing/tests/SharesUpdatedListenerTest.php index 74347e3382d69..6ddd338e757fd 100644 --- a/apps/files_sharing/tests/SharesUpdatedListenerTest.php +++ b/apps/files_sharing/tests/SharesUpdatedListenerTest.php @@ -19,6 +19,7 @@ use OCP\Share\Events\ShareCreatedEvent; use OCP\Share\IManager; use OCP\Share\IShare; +use OCP\User\Exceptions\UserNotFoundException; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; use Psr\Clock\ClockInterface; @@ -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 UserNotFoundException('Backends provided no user object'); + } + }); + + // the failure is logged, not thrown + $this->logger->expects($this->once())->method('warning'); + + // must not throw: user2 is still processed + $this->sharesUpdatedListener->handle($event); + } + public function testShareAccessUpdated() { $user1 = $this->createUser('user1', ''); $user2 = $this->createUser('user2', '');