diff --git a/apps/settings/composer/composer/autoload_classmap.php b/apps/settings/composer/composer/autoload_classmap.php index ef81604ca0e9f..7a6c733a5830b 100644 --- a/apps/settings/composer/composer/autoload_classmap.php +++ b/apps/settings/composer/composer/autoload_classmap.php @@ -114,6 +114,7 @@ 'OCA\\Settings\\SetupChecks\\SystemIs64bit' => $baseDir . '/../lib/SetupChecks/SystemIs64bit.php', 'OCA\\Settings\\SetupChecks\\TempSpaceAvailable' => $baseDir . '/../lib/SetupChecks/TempSpaceAvailable.php', 'OCA\\Settings\\SetupChecks\\TransactionIsolation' => $baseDir . '/../lib/SetupChecks/TransactionIsolation.php', + 'OCA\\Settings\\SetupChecks\\WellKnownUrls' => $baseDir . '/../lib/SetupChecks/WellKnownUrls.php', 'OCA\\Settings\\UserMigration\\AccountMigrator' => $baseDir . '/../lib/UserMigration/AccountMigrator.php', 'OCA\\Settings\\UserMigration\\AccountMigratorException' => $baseDir . '/../lib/UserMigration/AccountMigratorException.php', 'OCA\\Settings\\WellKnown\\ChangePasswordHandler' => $baseDir . '/../lib/WellKnown/ChangePasswordHandler.php', diff --git a/apps/settings/composer/composer/autoload_static.php b/apps/settings/composer/composer/autoload_static.php index fe23224d21493..0f011a7e8bd28 100644 --- a/apps/settings/composer/composer/autoload_static.php +++ b/apps/settings/composer/composer/autoload_static.php @@ -129,6 +129,7 @@ class ComposerStaticInitSettings 'OCA\\Settings\\SetupChecks\\SystemIs64bit' => __DIR__ . '/..' . '/../lib/SetupChecks/SystemIs64bit.php', 'OCA\\Settings\\SetupChecks\\TempSpaceAvailable' => __DIR__ . '/..' . '/../lib/SetupChecks/TempSpaceAvailable.php', 'OCA\\Settings\\SetupChecks\\TransactionIsolation' => __DIR__ . '/..' . '/../lib/SetupChecks/TransactionIsolation.php', + 'OCA\\Settings\\SetupChecks\\WellKnownUrls' => __DIR__ . '/..' . '/../lib/SetupChecks/WellKnownUrls.php', 'OCA\\Settings\\UserMigration\\AccountMigrator' => __DIR__ . '/..' . '/../lib/UserMigration/AccountMigrator.php', 'OCA\\Settings\\UserMigration\\AccountMigratorException' => __DIR__ . '/..' . '/../lib/UserMigration/AccountMigratorException.php', 'OCA\\Settings\\WellKnown\\ChangePasswordHandler' => __DIR__ . '/..' . '/../lib/WellKnown/ChangePasswordHandler.php', diff --git a/apps/settings/lib/AppInfo/Application.php b/apps/settings/lib/AppInfo/Application.php index 8318391a2e672..2c6744fe7d9f0 100644 --- a/apps/settings/lib/AppInfo/Application.php +++ b/apps/settings/lib/AppInfo/Application.php @@ -86,6 +86,7 @@ use OCA\Settings\SetupChecks\SystemIs64bit; use OCA\Settings\SetupChecks\TempSpaceAvailable; use OCA\Settings\SetupChecks\TransactionIsolation; +use OCA\Settings\SetupChecks\WellKnownUrls; use OCA\Settings\UserMigration\AccountMigrator; use OCA\Settings\WellKnown\ChangePasswordHandler; use OCA\Settings\WellKnown\SecurityTxtHandler; @@ -213,6 +214,7 @@ public function register(IRegistrationContext $context): void { $context->registerSetupCheck(TempSpaceAvailable::class); $context->registerSetupCheck(TransactionIsolation::class); $context->registerSetupCheck(PushService::class); + $context->registerSetupCheck(WellKnownUrls::class); $context->registerUserMigrator(AccountMigrator::class); } diff --git a/apps/settings/lib/SetupChecks/WellKnownUrls.php b/apps/settings/lib/SetupChecks/WellKnownUrls.php new file mode 100644 index 0000000000000..2ab86182e4f7c --- /dev/null +++ b/apps/settings/lib/SetupChecks/WellKnownUrls.php @@ -0,0 +1,118 @@ + + * + * @author Côme Chilliet + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Settings\SetupChecks; + +use OCP\Http\Client\IClientService; +use OCP\IConfig; +use OCP\IL10N; +use OCP\IRequest; +use OCP\IURLGenerator; +use OCP\SetupCheck\ISetupCheck; +use OCP\SetupCheck\SetupResult; + +class WellKnownUrls implements ISetupCheck { + public function __construct( + private IConfig $config, + private IL10N $l10n, + private IURLGenerator $urlGenerator, + private IRequest $request, + private IClientService $httpClientService, + ) { + } + + public function getCategory(): string { + return 'system'; + } + + public function getName(): string { + return $this->l10n->t('.well-known URLs'); + } + + /** + * @param 'get'|'propfind' $verb + */ + private function checkGetUrl(string $verb, string $url, array $validStatuses, bool $checkCustomHeader): bool { + $client = $this->httpClientService->newClient(); + $response = $client->$verb($this->urlGenerator->getAbsoluteURL($url), ['verify' => false, 'http_errors' => false]); + if (!in_array($response->getStatusCode(), $validStatuses)) { + return false; + } + if ($checkCustomHeader && empty($response->getHeader('X-NEXTCLOUD-WELL-KNOWN'))) { + return false; + } + return true; + } + + public function run(): SetupResult { + if (!$this->config->getSystemValueBool('check_for_working_wellknown_setup', true)) { + return SetupResult::success($this->l10n->t('`check_for_working_wellknown_setup` is set to false in your configuration, so this check was skipped.')); + } + try { + $checkList = ''; + $level = 'success'; + $urls = [ + ['get', '/.well-known/webfinger', [200, 404], true], + ['get', '/.well-known/nodeinfo', [200, 404], true], + ['propfind', '/.well-known/caldav', [207], false], + ['propfind', '/.well-known/carddav', [207], false], + ]; + foreach ($urls as [$verb,$url,$validStatuses,$checkCustomHeader]) { + if (!$this->checkGetUrl($verb, $url, $validStatuses, $checkCustomHeader)) { + $level = 'info'; + $checkList .= ' - '.strtoupper($verb).' '.$url.': failure'."\n"; + } else { + $checkList .= ' - '.strtoupper($verb).' '.$url.': success'."\n"; + } + } + return match($level) { + 'success' => SetupResult::success( + $this->l10n->t("Your web server is correctly configured to serve `.well-known` URLs:\n%s", [$checkList]), + $this->urlGenerator->linkToDocs('admin-setup-well-known-URL') + ), + 'info' => SetupResult::info( + $this->l10n->t("Your web server is not properly set up to resolve well-known URLs:\n%s", [$checkList]), + $this->urlGenerator->linkToDocs('admin-setup-well-known-URL') + ), + }; + } catch (\Exception $e) { + return SetupResult::error( + $this->l10n->t('Failed to test .well-known URLs: "%s".', [$e->getMessage()]), + ); + } + /* + * TODO: + // OC.SetupChecks.checkWellKnownUrl('PROPFIND', '/.well-known/caldav', OC.theme.docPlaceholderUrl), + // OC.SetupChecks.checkWellKnownUrl('PROPFIND', '/.well-known/carddav', OC.theme.docPlaceholderUrl), + OC.SetupChecks.checkProviderUrl(OC.getRootPath() + '/ocm-provider/', OC.theme.docPlaceholderUrl, $('#postsetupchecks').data('check-wellknown') === true), + OC.SetupChecks.checkProviderUrl(OC.getRootPath() + '/ocs-provider/', OC.theme.docPlaceholderUrl, $('#postsetupchecks').data('check-wellknown') === true), + Valid status is 207 + */ + return SetupResult::success( + $this->l10n->t('Your server is correctly configured to serve `.well-known` URLs.') + ); + } +} diff --git a/lib/private/Http/Client/Client.php b/lib/private/Http/Client/Client.php index 3bf43e6c07ec6..9b1e888ebe724 100644 --- a/lib/private/Http/Client/Client.php +++ b/lib/private/Http/Client/Client.php @@ -408,6 +408,42 @@ public function options(string $uri, array $options = []): IResponse { return new Response($response); } + /** + * Sends a PROPFIND request + * + * @param string $uri + * @param array $options Array such as + * 'query' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, + * 'timeout' => 5, + * @return IResponse + * @throws \Exception If the request could not get completed + */ + public function propfind(string $uri, array $options = []): IResponse { + $this->preventLocalAddress($uri, $options); + $response = $this->client->request('propfind', $uri, $this->buildRequestOptions($options)); + $isStream = isset($options['stream']) && $options['stream']; + return new Response($response, $isStream); + } + protected function wrapGuzzlePromise(PromiseInterface $promise): IPromise { return new GuzzlePromiseAdapter( $promise, diff --git a/lib/public/Http/Client/IClient.php b/lib/public/Http/Client/IClient.php index fb1760c25f216..affc74563cd33 100644 --- a/lib/public/Http/Client/IClient.php +++ b/lib/public/Http/Client/IClient.php @@ -207,6 +207,36 @@ public function delete(string $uri, array $options = []): IResponse; */ public function options(string $uri, array $options = []): IResponse; + /** + * Sends a PROPFIND request + * @param string $uri + * @param array $options Array such as + * 'query' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, + * @return IResponse + * @throws \Exception If the request could not get completed + * @since 29.0.0 + */ + public function propfind(string $uri, array $options = []): IResponse; + /** * Sends an asynchronous GET request * @param string $uri