diff --git a/lib/Command/CirclesCheck.php b/lib/Command/CirclesCheck.php index 019c55643..890c210d4 100644 --- a/lib/Command/CirclesCheck.php +++ b/lib/Command/CirclesCheck.php @@ -32,6 +32,7 @@ namespace OCA\Circles\Command; use ArtificialOwl\MySmallPhpTools\Exceptions\RequestNetworkException; +use ArtificialOwl\MySmallPhpTools\Exceptions\SignatoryException; use ArtificialOwl\MySmallPhpTools\Model\Nextcloud\nc22\NC22Request; use ArtificialOwl\MySmallPhpTools\Model\Request; use ArtificialOwl\MySmallPhpTools\Model\SimpleDataStore; @@ -39,6 +40,8 @@ use ArtificialOwl\MySmallPhpTools\Traits\TArrayTools; use ArtificialOwl\MySmallPhpTools\Traits\TStringTools; use Exception; +use OC; +use OC\AppConfig; use OC\Core\Command\Base; use OCA\Circles\AppInfo\Application; use OCA\Circles\AppInfo\Capabilities; @@ -50,11 +53,13 @@ use OCA\Circles\Exceptions\RemoteNotFoundException; use OCA\Circles\Exceptions\RemoteResourceNotFoundException; use OCA\Circles\Exceptions\RequestBuilderException; +use OCA\Circles\Exceptions\UnknownInterfaceException; use OCA\Circles\Exceptions\UnknownRemoteException; use OCA\Circles\FederatedItems\LoopbackTest; use OCA\Circles\Model\Federated\FederatedEvent; use OCA\Circles\Service\ConfigService; use OCA\Circles\Service\FederatedEventService; +use OCA\Circles\Service\InterfaceService; use OCA\Circles\Service\RemoteService; use OCA\Circles\Service\RemoteStreamService; use OCA\Circles\Service\RemoteUpstreamService; @@ -84,6 +89,9 @@ class CirclesCheck extends Base { /** @var Capabilities */ private $capabilities; + /** @var InterfaceService */ + private $interfaceService; + /** @var FederatedEventService */ private $federatedEventService; @@ -100,9 +108,6 @@ class CirclesCheck extends Base { private $configService; - /** @var int */ - private $delay = 5; - /** @var array */ private $sessions = []; @@ -111,7 +116,8 @@ class CirclesCheck extends Base { * CirclesCheck constructor. * * @param Capabilities $capabilities - * @param FederatedEvent $federatedEventService + * @param InterfaceService $interfaceService + * @param FederatedEventService $federatedEventService * @param RemoteService $remoteService * @param RemoteStreamService $remoteStreamService * @param RemoteUpstreamService $remoteUpstreamService @@ -119,6 +125,7 @@ class CirclesCheck extends Base { */ public function __construct( Capabilities $capabilities, + InterfaceService $interfaceService, FederatedEventService $federatedEventService, RemoteService $remoteService, RemoteStreamService $remoteStreamService, @@ -128,6 +135,7 @@ public function __construct( parent::__construct(); $this->capabilities = $capabilities; + $this->interfaceService = $interfaceService; $this->federatedEventService = $federatedEventService; $this->remoteService = $remoteService; $this->remoteStreamService = $remoteStreamService; @@ -140,9 +148,9 @@ protected function configure() { parent::configure(); $this->setName('circles:check') ->setDescription('Checking your configuration') - ->addOption('delay', 'd', InputOption::VALUE_REQUIRED, 'delay before checking result') ->addOption('capabilities', '', InputOption::VALUE_NONE, 'listing app\'s capabilities') ->addOption('type', '', InputOption::VALUE_REQUIRED, 'configuration to check', '') + ->addOption('alpha', '', InputOption::VALUE_NONE, 'allow ALPHA features') ->addOption('test', '', InputOption::VALUE_REQUIRED, 'specify an url to test', ''); } @@ -162,10 +170,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 0; } - if ($input->getOption('delay')) { - $this->delay = (int)$input->getOption('delay'); - } - $this->configService->setAppValue(ConfigService::TEST_NC_BASE, ''); $test = $input->getOption('test'); $type = $input->getOption('type'); @@ -184,72 +188,25 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln(''); $output->writeln(''); } + + if ($type === '' || $type === 'internal') { $output->writeln('### Testing internal address.'); $this->checkInternal($input, $output, $test); $output->writeln(''); $output->writeln(''); } + + if (!$input->getOption('alpha')) { + return 0; + } + if ($type === '' || $type === 'frontal') { $output->writeln('### Testing frontal address.'); $this->checkFrontal($input, $output, $test); $output->writeln(''); } - -// -// if (!$this->testRequest($output, 'GET', 'core.CSRFToken.index')) { -// $this->configService->setAppValue(ConfigService::TEST_NC_BASE, ''); -// -// return 0; -// } -// -// if (!$this->testRequest( -// $output, 'POST', 'circles.EventWrapper.asyncBroadcast', -// ['token' => 'test-dummy-token'] -// )) { -// $this->configService->setAppValue(ConfigService::TEST_NC_BASE, ''); -// -// return 0; -// } -// -// $test = new GSEvent(GSEvent::TEST, true, true); -// $test->setAsync(true); -// $token = $this->gsUpstreamService->newEvent($test); -// -// $output->writeln('- Async request is sent, now waiting ' . $this->delay . ' seconds'); -// sleep($this->delay); -// $output->writeln('- Pause is over, checking results for ' . $token); -// -// $wrappers = $this->gsUpstreamService->getEventsByToken($token); -// -// $result = []; -// $instances = array_merge($this->globalScaleService->getInstances(true)); -// foreach ($wrappers as $wrapper) { -// $result[$wrapper->getInstance()] = $wrapper->getEvent(); -// } -// -// $localLooksGood = false; -// foreach ($instances as $instance) { -// $output->write($instance . ' '); -// if (array_key_exists($instance, $result) -// && $result[$instance]->getResult() -// ->gInt('status') === 1) { -// $output->writeln('ok'); -// if ($this->configService->isLocalInstance($instance)) { -// $localLooksGood = true; -// } -// } else { -// $output->writeln('fail'); -// } -// } -// -// $this->configService->setAppValue(ConfigService::TEST_NC_BASE, ''); -// -// if ($localLooksGood) { -// $this->saveUrl($input, $output, $input->getOption('url')); -// } - return 0; } @@ -308,20 +265,22 @@ private function checkLoopback(InputInterface $input, OutputInterface $output, s } try { - [$scheme, $cloudId] = $this->parseAddress($loopback); + [$scheme, $cloudId, $path] = $this->parseAddress($loopback); } catch (Exception $e) { - $output->writeln('format must be http[s]://domain.name[:post]'); + $output->writeln('format must be http[s]://domain.name[:post][/path]'); continue; } - $loopback = $scheme . '://' . $cloudId; - $output->write('* testing address: ' . $loopback . ' '); + $loopback = rtrim($scheme . '://' . $cloudId . $path, '/'); + $output->writeln('* testing address: ' . $loopback . ' '); - if ($this->testLoopback($input, $output, $loopback)) { - $output->writeln('* Loopback address looks good'); + try { + $this->setupLoopback($input, $output, $loopback); $this->saveLoopback($input, $output, $loopback); return; + } catch (Exception $e) { + $output->writeln(''); } } } @@ -333,11 +292,12 @@ private function checkLoopback(InputInterface $input, OutputInterface $output, s private function setupLoopback(InputInterface $input, OutputInterface $output, string $address): void { $e = null; try { - [$scheme, $cloudId] = $this->parseAddress($address); + [$scheme, $cloudId, $path] = $this->parseAddress($address); $this->configService->setAppValue(ConfigService::LOOPBACK_TMP_SCHEME, $scheme); $this->configService->setAppValue(ConfigService::LOOPBACK_TMP_ID, $cloudId); - if (!$this->testLoopback($input, $output, $address)) { + $this->configService->setAppValue(ConfigService::LOOPBACK_TMP_PATH, $path); + if (!$this->testLoopback($input, $output)) { throw new Exception(); } } catch (Exception $e) { @@ -345,6 +305,7 @@ private function setupLoopback(InputInterface $input, OutputInterface $output, s $this->configService->setAppValue(ConfigService::LOOPBACK_TMP_SCHEME, ''); $this->configService->setAppValue(ConfigService::LOOPBACK_TMP_ID, ''); + $this->configService->setAppValue(ConfigService::LOOPBACK_TMP_PATH, ''); if (!is_null($e)) { throw $e; @@ -355,10 +316,8 @@ private function setupLoopback(InputInterface $input, OutputInterface $output, s /** * @param InputInterface $input * @param OutputInterface $output - * @param string $address * * @return bool - * @throws RequestNetworkException * @throws FederatedEventException * @throws FederatedItemException * @throws InitiatorNotConfirmedException @@ -366,10 +325,10 @@ private function setupLoopback(InputInterface $input, OutputInterface $output, s * @throws RemoteInstanceException * @throws RemoteNotFoundException * @throws RemoteResourceNotFoundException - * @throws UnknownRemoteException * @throws RequestBuilderException + * @throws UnknownRemoteException */ - private function testLoopback(InputInterface $input, OutputInterface $output, string $address): bool { + private function testLoopback(InputInterface $input, OutputInterface $output): bool { if (!$this->testRequest($output, 'GET', 'core.CSRFToken.index')) { return false; } @@ -387,14 +346,13 @@ private function testLoopback(InputInterface $input, OutputInterface $output, st $output->writeln('' . $test->getWrapperToken() . ''); - $output->writeln('- Waiting for async process to finish (' . $this->delay . 's)'); - sleep($this->delay); + $output->writeln('- Waiting for async process to finish (5s)'); + sleep(5); $output->write('- Checking status on FederatedEvent '); $wrappers = $this->remoteUpstreamService->getEventsByToken($test->getWrapperToken()); if (count($wrappers) !== 1) { $output->writeln('Event created too many Wrappers'); - $output->writeln('Event created too many Wrappers'); } $wrapper = array_shift($wrappers); @@ -431,10 +389,11 @@ private function testLoopback(InputInterface $input, OutputInterface $output, st * @throws Exception */ private function saveLoopback(InputInterface $input, OutputInterface $output, string $loopback): void { - [$scheme, $cloudId] = $this->parseAddress($loopback); + [$scheme, $cloudId, $path] = $this->parseAddress($loopback); $question = new ConfirmationQuestion( - '- Do you want to save '. $loopback . ' as your loopback address ? (y/N) ', false, '/^(y|Y)/i' + '- Do you want to save ' . $loopback + . ' as your loopback address ? (y/N) ', false, '/^(y|Y)/i' ); $helper = $this->getHelper('question'); @@ -446,6 +405,7 @@ private function saveLoopback(InputInterface $input, OutputInterface $output, st $this->configService->setAppValue(ConfigService::LOOPBACK_CLOUD_SCHEME, $scheme); $this->configService->setAppValue(ConfigService::LOOPBACK_CLOUD_ID, $cloudId); + $this->configService->setAppValue(ConfigService::LOOPBACK_CLOUD_PATH, $path); $output->writeln( '- Address ' . $loopback . ' is now used as loopback' ); @@ -455,6 +415,10 @@ private function saveLoopback(InputInterface $input, OutputInterface $output, st * @param InputInterface $input * @param OutputInterface $output * @param string $test + * + * @throws SignatoryException + * @throws UnknownInterfaceException + * @throws Exception */ private function checkInternal(InputInterface $input, OutputInterface $output, string $test): void { $output->writeln( @@ -463,6 +427,170 @@ private function checkInternal(InputInterface $input, OutputInterface $output, s $output->writeln( '. The address you need to define here is the local address of your Nextcloud, reachable by all other instances of our GlobalScale.' ); + + $question = new ConfirmationQuestion( + '- Do you want to enable this feature ? (y/N) ', false, '/^(y|Y)/i' + ); + + $helper = $this->getHelper('question'); + if (!$helper->ask($input, $output, $question)) { + $output->writeln('skipping.'); + + return; + } + + while (true) { + $output->writeln(''); + $question = new Question( + 'Please write down a new internal address to test: ', '' + ); + + $internal = $helper->ask($input, $output, $question); + if (is_null($internal) || $internal === '') { + $output->writeln('skipping.'); + + return; + } + + try { + [$scheme, $cloudId, $path] = $this->parseAddress($internal); + } catch (Exception $e) { + $output->writeln('format must be http[s]://domain.name[:post][/path]'); + continue; + } + + $internal = rtrim($scheme . '://' . $cloudId, '/'); + $fullInternal = rtrim($scheme . '://' . $cloudId . $path, '/'); + + $question = new ConfirmationQuestion( + 'Do you want to check the validity of this internal address? (Y/n) ', true, + '/^(y|Y)/i' + ); + + if ($helper->ask($input, $output, $question)) { + $testToken = $this->token(); + $this->configService->setAppValue(ConfigService::IFACE_TEST_ID, $cloudId); + $this->configService->setAppValue(ConfigService::IFACE_TEST_SCHEME, $scheme); + $this->configService->setAppValue(ConfigService::IFACE_TEST_PATH, $path); + $this->configService->setAppValue(ConfigService::IFACE_TEST_TOKEN, $testToken); + + $output->writeln(''); + $output->writeln( + 'You will need to run this curl command from a terminal on your local network and paste its result: ' + ); + $output->writeln( + ' curl -L "' . $internal + . '/.well-known/webfinger?resource=http://nextcloud.com/&test=' + . $testToken . '"' + ); + + $output->writeln('paste the result here: '); + $question = new Question('', ''); + $pastedWebfinger = new SimpleDataStore(); + $pastedWebfinger->json(trim($helper->ask($input, $output, $question))); + + if ($pastedWebfinger->g('subject') !== Application::APP_SUBJECT) { + $output->writeln('Cannot extract SUBJECT from the pasted data'); + continue; + } + + $pastedHref = ''; + foreach ($pastedWebfinger->gArray('links') as $link) { + $entry = new SimpleDataStore($link); + if ($entry->g('rel') === Application::APP_REL) { + $pastedHref = $entry->g('href'); + } + } + + if ($pastedHref === '') { + $output->writeln('Cannot retrieve HREF from the pasted data'); + continue; + } + + $href = $this->interfaceService->getCloudPath( + 'circles.Remote.appService', + [], + InterfaceService::IFACE_TEST + ); + + if ($pastedHref !== $href) { + $output->writeln( + 'The returned data (' . $pastedHref . ') are not the one expected: ' + . $href + ); + continue; + } + + $output->writeln(''); + $output->writeln('First step seems fine.'); + $output->writeln( + 'Now, please run this curl command from a terminal on your local network and paste its result: ' + ); + $output->writeln( + ' curl -L "' . $pastedHref . '?test=' . $testToken . '" -H "Accept: application/json"' + ); + + $output->writeln('paste the result here: '); + $question = new Question('', ''); + $pastedSignatory = new SimpleDataStore(); + $pastedSignatory->json(trim($helper->ask($input, $output, $question))); + + // small hack to refresh the cached config + OC::$server->get(AppConfig::class)->clearCachedConfig(); + $this->interfaceService->setCurrentInterface(InterfaceService::IFACE_TEST); + $appSignatory = $this->remoteStreamService->getAppSignatory(false); + + if ($appSignatory->getUid(true) !== $pastedSignatory->g('uid') + || $appSignatory->getRoot() !== $pastedSignatory->g('root')) { + $output->writeln( + 'The returned data (' + . $pastedSignatory->g('uid') . '/' . $pastedSignatory->g('root') + . ') are not the one expected: ' + . $appSignatory->getUid(true) . '/' . $appSignatory->getRoot() + ); + continue; + } + + $output->writeln('* Internal address looks good'); + } + + $this->saveInternal($input, $output, $fullInternal); + + return; + } + } + + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @param string $internal + * + * @throws Exception + */ + private function saveInternal(InputInterface $input, OutputInterface $output, string $internal): void { + [$scheme, $cloudId, $path] = $this->parseAddress($internal); + + $output->writeln(''); + $question = new ConfirmationQuestion( + '- Do you want to save ' . $internal + . ' as your internal address ? (y/N) ', false, '/^(y|Y)/i' + ); + + $helper = $this->getHelper('question'); + if (!$helper->ask($input, $output, $question)) { + $output->writeln('skipping.'); + + return; + } + + $this->configService->setAppValue(ConfigService::INTERNAL_CLOUD_SCHEME, $scheme); + $this->configService->setAppValue(ConfigService::INTERNAL_CLOUD_ID, $cloudId); + $this->configService->setAppValue(ConfigService::INTERNAL_CLOUD_PATH, $path); + + $output->writeln( + '- Address ' . $internal . ' is now used as internal' + ); } @@ -508,13 +636,13 @@ private function checkFrontal(InputInterface $input, OutputInterface $output, st } try { - [$scheme, $cloudId] = $this->parseAddress($frontal); + [$scheme, $cloudId, $path] = $this->parseAddress($frontal); } catch (Exception $e) { - $output->writeln('format must be http[s]://domain.name[:post]'); + $output->writeln('format must be http[s]://domain.name[:post][/path]'); continue; } - $frontal = $scheme . '://' . $cloudId; + $frontal = rtrim($scheme . '://' . $cloudId . $path, '/'); break; } @@ -729,15 +857,22 @@ private function parseAddress(string $test): array { $scheme = parse_url($test, PHP_URL_SCHEME); $cloudId = parse_url($test, PHP_URL_HOST); $cloudIdPort = parse_url($test, PHP_URL_PORT); + $path = parse_url($test, PHP_URL_PATH); - if (is_null($scheme) || is_null($cloudId)) { + if (is_bool($scheme) || is_bool($cloudId) || is_null($scheme) || is_null($cloudId)) { throw new Exception(); } + if (is_null($path) || is_bool($path)) { + $path = ''; + } + + $path = rtrim($path, '/'); + if (!is_null($cloudIdPort)) { $cloudId = $cloudId . ':' . $cloudIdPort; } - return [$scheme, $cloudId]; + return [$scheme, $cloudId, $path]; } } diff --git a/lib/Controller/RemoteController.php b/lib/Controller/RemoteController.php index 8d270ce9f..a1cabb253 100644 --- a/lib/Controller/RemoteController.php +++ b/lib/Controller/RemoteController.php @@ -157,19 +157,21 @@ public function __construct( * @PublicPage * @NoCSRFRequired * + * @param string $test + * * @return DataResponse * @throws NotLoggedInException * @throws SignatoryException * @throws UnknownInterfaceException */ - public function appService(): DataResponse { + public function appService(string $test = ''): DataResponse { try { $this->publicPageJsonLimited(); } catch (JsonNotRequestedException $e) { return new DataResponse(); } - $this->interfaceService->setCurrentInterfaceFromRequest($this->request); + $this->interfaceService->setCurrentInterfaceFromRequest($this->request, $test); $signatory = $this->remoteStreamService->getAppSignatory(false, $this->request->getParam('auth', '')); return new DataResponse($signatory); diff --git a/lib/Handlers/WebfingerHandler.php b/lib/Handlers/WebfingerHandler.php index b75c1dcb6..d3449db6d 100644 --- a/lib/Handlers/WebfingerHandler.php +++ b/lib/Handlers/WebfingerHandler.php @@ -111,7 +111,7 @@ public function handle(string $service, IRequestContext $context, ?IResponse $re } try { - $this->interfaceService->setCurrentInterfaceFromRequest($request, $request->getParam('check', '')); + $this->interfaceService->setCurrentInterfaceFromRequest($request, $request->getParam('test', '')); $this->remoteStreamService->getAppSignatory(); $href = $this->interfaceService->getCloudPath('circles.Remote.appService'); $info = [ diff --git a/lib/Service/ConfigService.php b/lib/Service/ConfigService.php index e0b74a15f..c2cbc73ae 100644 --- a/lib/Service/ConfigService.php +++ b/lib/Service/ConfigService.php @@ -32,8 +32,10 @@ namespace OCA\Circles\Service; use ArtificialOwl\MySmallPhpTools\Model\Nextcloud\nc22\NC22Request; +use ArtificialOwl\MySmallPhpTools\Traits\Nextcloud\nc22\TNC22Logger; use ArtificialOwl\MySmallPhpTools\Traits\TArrayTools; use ArtificialOwl\MySmallPhpTools\Traits\TStringTools; +use OC; use OCA\Circles\AppInfo\Application; use OCA\Circles\Exceptions\GSStatusException; use OCA\Circles\IFederatedUser; @@ -51,33 +53,46 @@ class ConfigService { use TStringTools; use TArrayTools; + use TNC22Logger; public const FRONTAL_CLOUD_BASE = 'frontal_cloud_base'; public const FRONTAL_CLOUD_ID = 'frontal_cloud_id'; public const FRONTAL_CLOUD_SCHEME = 'frontal_cloud_scheme'; + public const FRONTAL_CLOUD_PATH = 'frontal_cloud_path'; public const INTERNAL_CLOUD_ID = 'internal_cloud_id'; public const INTERNAL_CLOUD_SCHEME = 'internal_cloud_scheme'; + public const INTERNAL_CLOUD_PATH = 'internal_cloud_path'; public const LOOPBACK_CLOUD_ID = 'loopback_cloud_id'; public const LOOPBACK_CLOUD_SCHEME = 'loopback_cloud_scheme'; + public const LOOPBACK_CLOUD_PATH = 'loopback_cloud_path'; public const IFACE0_CLOUD_ID = 'iface0_cloud_id'; public const IFACE0_CLOUD_SCHEME = 'iface0_cloud_scheme'; + public const IFACE0_CLOUD_PATH = 'iface0_cloud_path'; public const IFACE0_INTERNAL = 'iface0_internal'; public const IFACE1_CLOUD_ID = 'iface1_cloud_id'; public const IFACE1_CLOUD_SCHEME = 'iface1_cloud_scheme'; + public const IFACE1_CLOUD_PATH = 'iface1_cloud_path'; public const IFACE1_INTERNAL = 'iface1_internal'; public const IFACE2_CLOUD_ID = 'iface2_cloud_id'; public const IFACE2_CLOUD_SCHEME = 'iface2_cloud_scheme'; + public const IFACE2_CLOUD_PATH = 'iface2_cloud_path'; public const IFACE2_INTERNAL = 'iface2_internal'; public const IFACE3_CLOUD_ID = 'iface3_cloud_id'; public const IFACE3_CLOUD_SCHEME = 'iface3_cloud_scheme'; + public const IFACE3_CLOUD_PATH = 'iface3_cloud_path'; public const IFACE3_INTERNAL = 'iface3_internal'; public const IFACE4_CLOUD_ID = 'iface4_cloud_id'; public const IFACE4_CLOUD_SCHEME = 'iface4_cloud_scheme'; + public const IFACE4_CLOUD_PATH = 'iface4_cloud_path'; public const IFACE4_INTERNAL = 'iface4_internal'; public const IFACE_TEST_ID = 'iface_test_id'; public const IFACE_TEST_SCHEME = 'iface_test_scheme'; + public const IFACE_TEST_PATH = 'iface_test_path'; public const IFACE_TEST_TOKEN = 'iface_test_token'; + public const LOOPBACK_TMP_ID = 'loopback_tmp_id'; + public const LOOPBACK_TMP_SCHEME = 'loopback_tmp_scheme'; + public const LOOPBACK_TMP_PATH = 'loopback_tmp_path'; public const HARD_MODERATION = 'hard_moderation'; public const FRONTEND_ENABLED = 'frontend_enabled'; @@ -96,9 +111,6 @@ class ConfigService { public const MAINTENANCE_UPDATE = 'maintenance_update'; public const MAINTENANCE_RUN = 'maintenance_run'; - public const LOOPBACK_TMP_ID = 'loopback_tmp_id'; - public const LOOPBACK_TMP_SCHEME = 'loopback_tmp_scheme'; - public const GS_MODE = 'mode'; public const GS_KEY = 'key'; @@ -119,30 +131,40 @@ class ConfigService { self::FRONTAL_CLOUD_BASE => '', self::FRONTAL_CLOUD_ID => '', self::FRONTAL_CLOUD_SCHEME => 'https', + self::FRONTAL_CLOUD_PATH => '', self::INTERNAL_CLOUD_ID => '', self::INTERNAL_CLOUD_SCHEME => 'https', + self::INTERNAL_CLOUD_PATH => '', self::LOOPBACK_CLOUD_ID => '', self::LOOPBACK_CLOUD_SCHEME => 'https', - self::LOOPBACK_TMP_ID => '', - self::LOOPBACK_TMP_SCHEME => '', + self::LOOPBACK_CLOUD_PATH => '', self::IFACE0_CLOUD_ID => '', self::IFACE0_CLOUD_SCHEME => 'https', + self::IFACE0_CLOUD_PATH => '', self::IFACE0_INTERNAL => '0', self::IFACE1_CLOUD_ID => '', self::IFACE1_CLOUD_SCHEME => 'https', + self::IFACE1_CLOUD_PATH => '', self::IFACE1_INTERNAL => '0', self::IFACE2_CLOUD_ID => '', self::IFACE2_CLOUD_SCHEME => 'https', + self::IFACE2_CLOUD_PATH => '', self::IFACE2_INTERNAL => '0', self::IFACE3_CLOUD_ID => '', self::IFACE3_CLOUD_SCHEME => 'https', + self::IFACE3_CLOUD_PATH => '', self::IFACE3_INTERNAL => '0', self::IFACE4_CLOUD_ID => '', self::IFACE4_CLOUD_SCHEME => 'https', + self::IFACE4_CLOUD_PATH => '', self::IFACE4_INTERNAL => '0', self::IFACE_TEST_ID => '', self::IFACE_TEST_SCHEME => 'https', + self::IFACE_TEST_PATH => '', self::IFACE_TEST_TOKEN => '', + self::LOOPBACK_TMP_ID => '', + self::LOOPBACK_TMP_SCHEME => '', + self::LOOPBACK_TMP_PATH => '', self::FRONTEND_ENABLED => '1', self::HARD_MODERATION => '0', @@ -184,6 +206,8 @@ class ConfigService { public function __construct(IConfig $config, IURLGenerator $urlGenerator) { $this->config = $config; $this->urlGenerator = $urlGenerator; + + $this->setup('app', Application::APP_ID); } @@ -464,6 +488,11 @@ public function getLoopbackInstance(): string { $this->setAppValue(self::LOOPBACK_TMP_SCHEME, $loopback['scheme']); } + if (array_key_exists('path', $loopback) + && $this->getAppValue(self::LOOPBACK_TMP_PATH) !== $loopback['path']) { + $this->setAppValue(self::LOOPBACK_TMP_PATH, $loopback['path']); + } + return $loopbackCloudId; } @@ -483,12 +512,18 @@ public function getLoopbackPath(string $route = '', array $args = []): string { $scheme = $this->getAppValue(self::LOOPBACK_CLOUD_SCHEME); } - $base = $scheme . '://' . $instance; + $path = $this->getAppValue(self::LOOPBACK_TMP_PATH); + if ($path === '') { + $path = $this->getAppValue(self::LOOPBACK_CLOUD_PATH); + } + + $base = $scheme . '://' . $instance . $path; + if ($route === '') { return $base; } - return $base . $this->urlGenerator->linkToRoute($route, $args); + return rtrim($base, '/') . $this->linkToRoute($route, $args); } @@ -640,4 +675,33 @@ public function configureRequest(NC22Request $request): void { $request->setFollowLocation(true); $request->setTimeout(5); } + + + /** + * @param string $route + * @param array $args + * + * @return string + */ + public function linkToRoute(string $route, array $args): string { + $path = $this->urlGenerator->linkToRoute($route, $args); + + if (OC::$CLI) { + $knownPath = parse_url($this->config->getSystemValue('overwrite.cli.url'), PHP_URL_PATH); + } else { + $knownPath = OC::$WEBROOT; + } + + $knownPath = rtrim($knownPath, '/'); + if ($knownPath === '') { + return $path; + } + + $pos = strpos($path, $knownPath); + if ($pos === 0) { + return substr($path, strlen($knownPath)); + } + + return $path; + } } diff --git a/lib/Service/InterfaceService.php b/lib/Service/InterfaceService.php index 7e556be90..33eac4b24 100644 --- a/lib/Service/InterfaceService.php +++ b/lib/Service/InterfaceService.php @@ -369,31 +369,39 @@ public function getCloudPath(string $route = '', array $args = [], int $interfac $interface = $this->getCurrentInterface(); } - $scheme = ''; + $scheme = $path = ''; switch ($interface) { case self::IFACE_INTERNAL: $scheme = $this->configService->getAppValue(ConfigService::INTERNAL_CLOUD_SCHEME); + $path = $this->configService->getAppValue(ConfigService::INTERNAL_CLOUD_PATH); break; case self::IFACE_FRONTAL: $scheme = $this->configService->getAppValue(ConfigService::FRONTAL_CLOUD_SCHEME); + $path = $this->configService->getAppValue(ConfigService::FRONTAL_CLOUD_PATH); break; case self::IFACE0: $scheme = $this->configService->getAppValue(ConfigService::IFACE0_CLOUD_SCHEME); + $path = $this->configService->getAppValue(ConfigService::IFACE0_CLOUD_PATH); break; case self::IFACE1: $scheme = $this->configService->getAppValue(ConfigService::IFACE1_CLOUD_SCHEME); + $path = $this->configService->getAppValue(ConfigService::IFACE1_CLOUD_PATH); break; case self::IFACE2: $scheme = $this->configService->getAppValue(ConfigService::IFACE2_CLOUD_SCHEME); + $path = $this->configService->getAppValue(ConfigService::IFACE2_CLOUD_PATH); break; case self::IFACE3: $scheme = $this->configService->getAppValue(ConfigService::IFACE3_CLOUD_SCHEME); + $path = $this->configService->getAppValue(ConfigService::IFACE3_CLOUD_PATH); break; case self::IFACE4: $scheme = $this->configService->getAppValue(ConfigService::IFACE4_CLOUD_SCHEME); + $path = $this->configService->getAppValue(ConfigService::IFACE4_CLOUD_PATH); break; case self::IFACE_TEST: $scheme = $this->configService->getAppValue(ConfigService::IFACE_TEST_SCHEME); + $path = $this->configService->getAppValue(ConfigService::IFACE_TEST_PATH); break; } @@ -401,13 +409,12 @@ public function getCloudPath(string $route = '', array $args = [], int $interfac throw new UnknownInterfaceException('misconfigured scheme'); } - $base = $scheme . '://' . $this->getCloudInstance(); - + $base = $scheme . '://' . $this->getCloudInstance($interface) . $path; if ($route === '') { return $base; } - return $base . $this->urlGenerator->linkToRoute($route, $args); + return $base . $this->configService->linkToRoute($route, $args); } @@ -440,7 +447,7 @@ public function getLocalPath(string $route, array $args): string { return $this->configService->getLoopbackPath($route, $args); } - return rtrim($base, '/') . $this->urlGenerator->linkToRoute($route, $args); + return rtrim($base, '/') . $this->configService->linkToRoute($route, $args); }