diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index be06cdd45b860..a84bd24af4dd8 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -1056,6 +1056,7 @@ 'OCP\\TaskProcessing\\TaskTypes\\TextToTextSummary' => $baseDir . '/lib/public/TaskProcessing/TaskTypes/TextToTextSummary.php', 'OCP\\TaskProcessing\\TaskTypes\\TextToTextTopics' => $baseDir . '/lib/public/TaskProcessing/TaskTypes/TextToTextTopics.php', 'OCP\\TaskProcessing\\TaskTypes\\TextToTextTranslate' => $baseDir . '/lib/public/TaskProcessing/TaskTypes/TextToTextTranslate.php', + 'OCP\\Teams\\ITeamFileResolver' => $baseDir . '/lib/public/Teams/ITeamFileResolver.php', 'OCP\\Teams\\ITeamFolderProvider' => $baseDir . '/lib/public/Teams/ITeamFolderProvider.php', 'OCP\\Teams\\ITeamManager' => $baseDir . '/lib/public/Teams/ITeamManager.php', 'OCP\\Teams\\ITeamResourceProvider' => $baseDir . '/lib/public/Teams/ITeamResourceProvider.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 73277b5502fe1..5994bcd56b3b6 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -1097,6 +1097,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\TaskProcessing\\TaskTypes\\TextToTextSummary' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/TaskTypes/TextToTextSummary.php', 'OCP\\TaskProcessing\\TaskTypes\\TextToTextTopics' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/TaskTypes/TextToTextTopics.php', 'OCP\\TaskProcessing\\TaskTypes\\TextToTextTranslate' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/TaskTypes/TextToTextTranslate.php', + 'OCP\\Teams\\ITeamFileResolver' => __DIR__ . '/../../..' . '/lib/public/Teams/ITeamFileResolver.php', 'OCP\\Teams\\ITeamFolderProvider' => __DIR__ . '/../../..' . '/lib/public/Teams/ITeamFolderProvider.php', 'OCP\\Teams\\ITeamManager' => __DIR__ . '/../../..' . '/lib/public/Teams/ITeamManager.php', 'OCP\\Teams\\ITeamResourceProvider' => __DIR__ . '/../../..' . '/lib/public/Teams/ITeamResourceProvider.php', diff --git a/lib/private/Teams/TeamManager.php b/lib/private/Teams/TeamManager.php index d17aefe36223b..8289150b2e521 100644 --- a/lib/private/Teams/TeamManager.php +++ b/lib/private/Teams/TeamManager.php @@ -15,6 +15,7 @@ use OCA\Circles\Model\Probes\CircleProbe; use OCP\IURLGenerator; use OCP\Server; +use OCP\Teams\ITeamFileResolver; use OCP\Teams\ITeamFolderProvider; use OCP\Teams\ITeamManager; use OCP\Teams\ITeamResourceProvider; @@ -23,6 +24,11 @@ use Psr\Container\NotFoundExceptionInterface; class TeamManager implements ITeamManager { + /** + * Id of the provider whose resource ids are file ids. Owned by the circles + * app, and hardcoded by every caller of the teams resource route. + */ + private const FILE_ADDRESSED_PROVIDER_ID = 'files'; /** @var ?ITeamResourceProvider[] */ private ?array $providers = null; @@ -130,8 +136,31 @@ public function getTeamsForResource(string $providerId, string $resourceId, stri return []; } + return array_map($this->circleToTeam(...), $this->getTeams($this->collectTeamIds($providerId, $resourceId), $userId)); + } + + /** + * When the lookup addressed resources by file id, ask every file resolver as + * well: a team folder is mounted rather than shared, so a share lookup alone + * never finds it. + * + * @return list + */ + private function collectTeamIds(string $providerId, string $resourceId): array { $provider = $this->getProvider($providerId); - return array_map($this->circleToTeam(...), $this->getTeams($provider->getTeamsForResource($resourceId), $userId)); + $teamIds = $provider->getTeamsForResource($resourceId); + + if ($providerId === self::FILE_ADDRESSED_PROVIDER_ID) { + foreach ($this->getProviders() as $candidate) { + if ($candidate === $provider || !$candidate instanceof ITeamFileResolver) { + continue; + } + + $teamIds = array_merge($teamIds, $candidate->getTeamsForFile((int)$resourceId)); + } + } + + return array_values(array_unique($teamIds)); } private function getTeamInternal(string $teamId, string $userId, ?CircleProbe $probe = null): ?Circle { diff --git a/lib/public/Teams/ITeamFileResolver.php b/lib/public/Teams/ITeamFileResolver.php new file mode 100644 index 0000000000000..c7eadf18211f6 --- /dev/null +++ b/lib/public/Teams/ITeamFileResolver.php @@ -0,0 +1,39 @@ + Team ids, empty when the file is unrelated to this provider. + * @since 35.0.0 + */ + public function getTeamsForFile(int $fileId): array; +} diff --git a/tests/lib/Teams/TeamManagerTest.php b/tests/lib/Teams/TeamManagerTest.php index b5b1a42aca124..37661664ee537 100644 --- a/tests/lib/Teams/TeamManagerTest.php +++ b/tests/lib/Teams/TeamManagerTest.php @@ -12,6 +12,7 @@ use OC\AppFramework\Bootstrap\Coordinator; use OC\Teams\TeamManager; use OCP\IURLGenerator; +use OCP\Teams\ITeamFileResolver; use OCP\Teams\ITeamFolderProvider; use OCP\Teams\ITeamResourceProvider; use Test\TestCase; @@ -43,6 +44,75 @@ public function testGetTeamFolderProviderReturnsRegisteredFolderProvider(): void $this->assertSame($folderProvider, $teamManager->getTeamFolderProvider()); } + public function testCollectTeamIdsDoesNotWidenForOtherProviders(): void { + $teamManager = $this->createTeamManager(true); + $addressed = $this->createMock(ITeamResourceProvider::class); + $addressed->expects($this->once()) + ->method('getTeamsForResource') + ->with('42') + ->willReturn(['team-1']); + + $resolver = $this->createMock(ITeamFileResolver::class); + $resolver->expects($this->never())->method('getTeamsForFile'); + + $this->setProviders($teamManager, ['deck' => $addressed, 'groupfolders' => $resolver]); + + $this->assertSame(['team-1'], $this->collectTeamIds($teamManager, 'deck', '42')); + } + + public function testCollectTeamIdsAsksEveryResolverForTheFilesProvider(): void { + $teamManager = $this->createTeamManager(true); + $addressed = $this->createMock(ITeamResourceProvider::class); + $addressed->method('getTeamsForResource')->with('42')->willReturn(['team-1']); + + $resolver = $this->createMock(ITeamFileResolver::class); + $resolver->expects($this->once()) + ->method('getTeamsForFile') + ->with(42) + ->willReturn(['team-2']); + + $unrelated = $this->createMock(ITeamResourceProvider::class); + $unrelated->expects($this->never())->method('getTeamsForResource'); + + $this->setProviders($teamManager, [ + 'files' => $addressed, + 'groupfolders' => $resolver, + 'talk' => $unrelated, + ]); + + $this->assertSame(['team-1', 'team-2'], $this->collectTeamIds($teamManager, 'files', '42')); + } + + public function testCollectTeamIdsDoesNotAskTheAddressedProviderTwice(): void { + $teamManager = $this->createTeamManager(true); + $addressed = $this->createMock(ITeamFileResolver::class); + $addressed->method('getTeamsForResource')->willReturn(['team-1']); + $addressed->expects($this->never())->method('getTeamsForFile'); + + $this->setProviders($teamManager, ['files' => $addressed]); + + $this->assertSame(['team-1'], $this->collectTeamIds($teamManager, 'files', '42')); + } + + public function testCollectTeamIdsReturnsEachTeamOnce(): void { + $teamManager = $this->createTeamManager(true); + $addressed = $this->createMock(ITeamResourceProvider::class); + $addressed->method('getTeamsForResource')->willReturn(['team-1']); + + $resolver = $this->createMock(ITeamFileResolver::class); + $resolver->method('getTeamsForFile')->willReturn(['team-1', 'team-2']); + + $this->setProviders($teamManager, ['files' => $addressed, 'groupfolders' => $resolver]); + + $this->assertSame(['team-1', 'team-2'], $this->collectTeamIds($teamManager, 'files', '42')); + } + + private function collectTeamIds(TeamManager $teamManager, string $providerId, string $resourceId): array { + $method = new \ReflectionMethod(TeamManager::class, 'collectTeamIds'); + + return $method->invoke($teamManager, $providerId, $resourceId); + } + private function createTeamManager(bool $hasTeamSupport = false): TeamManager { return new class($this->createMock(Coordinator::class), $this->createMock(IURLGenerator::class), null, $hasTeamSupport, ) extends TeamManager { public function __construct(