diff --git a/lib/Service/FederationService.php b/lib/Service/FederationService.php index 12094fb4ae..aa9d303514 100644 --- a/lib/Service/FederationService.php +++ b/lib/Service/FederationService.php @@ -24,6 +24,7 @@ use OCP\ICacheFactory; use OCP\IRequest; use OCP\IURLGenerator; +use OCP\Security\ITrustedDomainHelper; use OCP\Share\IShare; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; @@ -43,6 +44,7 @@ public function __construct( private AppConfig $appConfig, private IRequest $request, private IURLGenerator $urlGenerator, + private ITrustedDomainHelper $trustedDomainHelper, ) { $this->cache = $cacheFactory->createDistributed('richdocuments_remote/'); try { @@ -73,6 +75,11 @@ public function getRemoteCollaboraURL($remote) { if (!$this->isTrustedRemote($remote)) { throw new \Exception('Unable to determine collabora URL of remote server ' . $remote . ' - Remote is not a trusted server'); } + + if ($this->trustedDomainHelper->isTrustedUrl($remote)) { + return $this->appConfig->getCollaboraUrlInternal(); + } + $remoteCollabora = $this->cache->get('richdocuments_remote/' . $remote); if ($remoteCollabora !== null) { return $remoteCollabora; @@ -112,7 +119,12 @@ public function isTrustedRemote($domainWithPort) { if (!is_string($trusted)) { break; } + + // This regular expression ensures that wildcards for trusted domains + // are parsed properly in order to match subdomains: + // *.example.com => /^[-\.a-zA-Z0-9]*\.example\.com$/i $regex = '/^' . implode('[-\.a-zA-Z0-9]*', array_map(fn ($v) => preg_quote($v, '/'), explode('*', $trusted))) . '$/i'; + if (preg_match($regex, $domain) || preg_match($regex, $domainWithPort)) { return true; } diff --git a/tests/lib/Service/FederationServiceTest.php b/tests/lib/Service/FederationServiceTest.php new file mode 100644 index 0000000000..b9a13c3686 --- /dev/null +++ b/tests/lib/Service/FederationServiceTest.php @@ -0,0 +1,84 @@ +cacheFactory = $this->createMock(ICacheFactory::class); + $this->clientService = $this->createMock(IClientService::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->tokenManager = $this->createMock(TokenManager::class); + $this->appConfig = $this->createStub(AppConfig::class); + $this->request = $this->createMock(IRequest::class); + $this->urlGenerator = $this->createMock(IURLGenerator::class); + $this->trustedDomainHelper = $this->createStub(ITrustedDomainHelper::class); + + $this->federationService = new FederationService( + $this->cacheFactory, + $this->clientService, + $this->logger, + $this->tokenManager, + $this->appConfig, + $this->request, + $this->urlGenerator, + $this->trustedDomainHelper + ); + } + + /** + * @test + * @testdox returns own instance's Collabora URL + */ + public function getRemoteCollaboraURLFromOwnInstance(): void { + // Ensure that trusted domains can be used for federated editing + $this->appConfig->method('isTrustedDomainAllowedForFederation') + ->willReturn(true); + $this->appConfig->method('getCollaboraUrlInternal') + ->willReturn(self::COLLABORA_ADDRESS); + + $this->trustedDomainHelper->method('isTrustedUrl') + ->with(self::NEXTCLOUD_ADDRESS) + ->willReturn(true); + + // Create a stub TrustedServers class which always tells us + // the server is trusted + $trustedServers = $this->createStub(TrustedServers::class); + $trustedServers->method('isTrustedServer') + ->with('nextcloud.local') + ->willReturn(true); + + // Do some reflection property manipulation to set the TrustedServers object + // It would be nice if the TrustedServers were passed into FederationService + // instead of being set manually in the constructor to make testing easier + $reflection = new \ReflectionClass($this->federationService); + $reflectionProperty = $reflection->getProperty('trustedServers'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->federationService, $trustedServers); + + $this->assertEquals(self::COLLABORA_ADDRESS, $this->federationService->getRemoteCollaboraURL(self::NEXTCLOUD_ADDRESS)); + } +}