aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.htaccess4
-rw-r--r--apps/settings/js/admin.js8
-rw-r--r--apps/settings/lib/AppInfo/Application.php21
-rw-r--r--core/Controller/WellKnownController.php67
-rw-r--r--core/js/setupchecks.js7
-rw-r--r--core/js/tests/specs/setupchecksSpec.js8
-rw-r--r--core/routes.php3
-rw-r--r--lib/composer/composer/autoload_classmap.php7
-rw-r--r--lib/composer/composer/autoload_static.php7
-rw-r--r--lib/private/AppFramework/Bootstrap/RegistrationContext.php24
-rw-r--r--lib/private/Http/WellKnown/RequestManager.php124
-rw-r--r--lib/public/AppFramework/Bootstrap/IRegistrationContext.php14
-rw-r--r--lib/public/Http/WellKnown/GenericResponse.php51
-rw-r--r--lib/public/Http/WellKnown/IHandler.php48
-rw-r--r--lib/public/Http/WellKnown/IRequestContext.php46
-rw-r--r--lib/public/Http/WellKnown/IResponse.php39
-rw-r--r--lib/public/Http/WellKnown/JrdResponse.php170
-rw-r--r--tests/Core/Controller/WellKnownControllerTest.php86
-rw-r--r--tests/lib/Http/WellKnown/GenericResponseTest.php40
-rw-r--r--tests/lib/Http/WellKnown/JrdResponseTest.php108
-rw-r--r--tests/lib/Http/WellKnown/RequestManagerTest.php176
21 files changed, 1022 insertions, 36 deletions
diff --git a/.htaccess b/.htaccess
index 87280cc9e01..8f13d050975 100644
--- a/.htaccess
+++ b/.htaccess
@@ -61,10 +61,6 @@
RewriteCond %{HTTP_USER_AGENT} DavClnt
RewriteRule ^$ /remote.php/webdav/ [L,R=302]
RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
- RewriteRule ^\.well-known/host-meta /public.php?service=host-meta [QSA,L]
- RewriteRule ^\.well-known/host-meta\.json /public.php?service=host-meta-json [QSA,L]
- RewriteRule ^\.well-known/webfinger /public.php?service=webfinger [QSA,L]
- RewriteRule ^\.well-known/nodeinfo /public.php?service=nodeinfo [QSA,L]
RewriteRule ^\.well-known/carddav /remote.php/dav/ [R=301,L]
RewriteRule ^\.well-known/caldav /remote.php/dav/ [R=301,L]
RewriteRule ^remote/(.*) remote.php [QSA,L]
diff --git a/apps/settings/js/admin.js b/apps/settings/js/admin.js
index 9252125de12..cffaefa3821 100644
--- a/apps/settings/js/admin.js
+++ b/apps/settings/js/admin.js
@@ -256,10 +256,10 @@ window.addEventListener('DOMContentLoaded', function(){
// run setup checks then gather error messages
$.when(
OC.SetupChecks.checkWebDAV(),
- OC.SetupChecks.checkWellKnownUrl('/.well-known/webfinger', OC.theme.docPlaceholderUrl, $('#postsetupchecks').data('check-wellknown') === true && !!OC.appConfig.core.public_webfinger, [200, 404]),
- OC.SetupChecks.checkWellKnownUrl('/.well-known/nodeinfo', OC.theme.docPlaceholderUrl, $('#postsetupchecks').data('check-wellknown') === true && !!OC.appConfig.core.public_nodeinfo, [200, 404]),
- OC.SetupChecks.checkWellKnownUrl('/.well-known/caldav', OC.theme.docPlaceholderUrl, $('#postsetupchecks').data('check-wellknown') === true),
- OC.SetupChecks.checkWellKnownUrl('/.well-known/carddav', OC.theme.docPlaceholderUrl, $('#postsetupchecks').data('check-wellknown') === true),
+ OC.SetupChecks.checkWellKnownUrl('GET', '/.well-known/webfinger', OC.theme.docPlaceholderUrl, $('#postsetupchecks').data('check-wellknown') === true, [200, 404], true),
+ OC.SetupChecks.checkWellKnownUrl('GET', '/.well-known/nodeinfo', OC.theme.docPlaceholderUrl, $('#postsetupchecks').data('check-wellknown') === true, [200, 404], true),
+ OC.SetupChecks.checkWellKnownUrl('PROPFIND', '/.well-known/caldav', OC.theme.docPlaceholderUrl, $('#postsetupchecks').data('check-wellknown') === true),
+ OC.SetupChecks.checkWellKnownUrl('PROPFIND', '/.well-known/carddav', OC.theme.docPlaceholderUrl, $('#postsetupchecks').data('check-wellknown') === true),
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),
OC.SetupChecks.checkSetup(),
diff --git a/apps/settings/lib/AppInfo/Application.php b/apps/settings/lib/AppInfo/Application.php
index fe9ba63b012..af45c56e517 100644
--- a/apps/settings/lib/AppInfo/Application.php
+++ b/apps/settings/lib/AppInfo/Application.php
@@ -163,8 +163,6 @@ class Application extends App implements IBootstrap {
$groupManager->listen('\OC\Group', 'postRemoveUser', [$this, 'removeUserFromGroup']);
$groupManager->listen('\OC\Group', 'postAddUser', [$this, 'addUserToGroup']);
});
-
- Util::connectHook('\OCP\Config', 'js', $this, 'extendJsConfig');
}
public function addUserToGroup(IGroup $group, IUser $user): void {
@@ -209,23 +207,4 @@ class Application extends App implements IBootstrap {
$hooks = $this->getContainer()->query(Hooks::class);
$hooks->onChangeEmail($parameters['user'], $parameters['old_value']);
}
-
- /**
- * @param array $settings
- */
- public function extendJsConfig(array $settings) {
- $appConfig = json_decode($settings['array']['oc_appconfig'], true);
-
- $publicWebFinger = \OC::$server->getConfig()->getAppValue('core', 'public_webfinger', '');
- if (!empty($publicWebFinger)) {
- $appConfig['core']['public_webfinger'] = $publicWebFinger;
- }
-
- $publicNodeInfo = \OC::$server->getConfig()->getAppValue('core', 'public_nodeinfo', '');
- if (!empty($publicNodeInfo)) {
- $appConfig['core']['public_nodeinfo'] = $publicNodeInfo;
- }
-
- $settings['array']['oc_appconfig'] = json_encode($appConfig);
- }
}
diff --git a/core/Controller/WellKnownController.php b/core/Controller/WellKnownController.php
new file mode 100644
index 00000000000..1d45e87569b
--- /dev/null
+++ b/core/Controller/WellKnownController.php
@@ -0,0 +1,67 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+namespace OC\Core\Controller;
+
+use OC\Http\WellKnown\RequestManager;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\JSONResponse;
+use OCP\AppFramework\Http\Response;
+use OCP\IRequest;
+
+class WellKnownController extends Controller {
+
+ /** @var RequestManager */
+ private $requestManager;
+
+ public function __construct(IRequest $request,
+ RequestManager $wellKnownManager) {
+ parent::__construct('core', $request);
+ $this->requestManager = $wellKnownManager;
+ }
+
+ /**
+ * @PublicPage
+ * @NoCSRFRequired
+ *
+ * @return Response
+ */
+ public function handle(string $service): Response {
+ $response = $this->requestManager->process(
+ $service,
+ $this->request
+ );
+
+ if ($response === null) {
+ $httpResponse = new JSONResponse(["message" => "$service not supported"], Http::STATUS_NOT_FOUND);
+ } else {
+ $httpResponse = $response->toHttpResponse();
+ }
+
+ // We add a custom header so that setup checks can detect if their requests are answered by this controller
+ return $httpResponse->addHeader('X-NEXTCLOUD-WELL-KNOWN', '1');
+ }
+}
diff --git a/core/js/setupchecks.js b/core/js/setupchecks.js
index 22c8589f73b..fb01a91b30e 100644
--- a/core/js/setupchecks.js
+++ b/core/js/setupchecks.js
@@ -56,7 +56,7 @@
* @param {int|int[]} expectedStatus the expected HTTP status to be returned by the URL, 207 by default
* @return $.Deferred object resolved with an array of error messages
*/
- checkWellKnownUrl: function(url, placeholderUrl, runCheck, expectedStatus) {
+ checkWellKnownUrl: function(verb, url, placeholderUrl, runCheck, expectedStatus, checkCustomHeader) {
if (expectedStatus === undefined) {
expectedStatus = [207];
}
@@ -73,7 +73,8 @@
}
var afterCall = function(xhr) {
var messages = [];
- if (expectedStatus.indexOf(xhr.status) === -1) {
+ var customWellKnown = xhr.getResponseHeader('X-NEXTCLOUD-WELL-KNOWN')
+ if (expectedStatus.indexOf(xhr.status) === -1 || (checkCustomHeader && !customWellKnown)) {
var docUrl = placeholderUrl.replace('PLACEHOLDER', 'admin-setup-well-known-URL');
messages.push({
msg: t('core', 'Your web server is not properly set up to resolve "{url}". Further information can be found in the <a target="_blank" rel="noreferrer noopener" href="{docLink}">documentation</a>.', { docLink: docUrl, url: url }),
@@ -84,7 +85,7 @@
};
$.ajax({
- type: 'PROPFIND',
+ type: verb,
url: url,
complete: afterCall,
allowAuthErrors: true
diff --git a/core/js/tests/specs/setupchecksSpec.js b/core/js/tests/specs/setupchecksSpec.js
index c3cddb88a9d..3f02302ee80 100644
--- a/core/js/tests/specs/setupchecksSpec.js
+++ b/core/js/tests/specs/setupchecksSpec.js
@@ -62,7 +62,7 @@ describe('OC.SetupChecks tests', function() {
describe('checkWellKnownUrl', function() {
it('should fail with another response status code than the expected one', function(done) {
- var async = OC.SetupChecks.checkWellKnownUrl('/.well-known/caldav', 'http://example.org/PLACEHOLDER', true, 207);
+ var async = OC.SetupChecks.checkWellKnownUrl('PROPFIND', '/.well-known/caldav', 'http://example.org/PLACEHOLDER', true, 207);
suite.server.requests[0].respond(200);
@@ -76,7 +76,7 @@ describe('OC.SetupChecks tests', function() {
});
it('should return no error with the expected response status code', function(done) {
- var async = OC.SetupChecks.checkWellKnownUrl('/.well-known/caldav', 'http://example.org/PLACEHOLDER', true, 207);
+ var async = OC.SetupChecks.checkWellKnownUrl('PROPFIND', '/.well-known/caldav', 'http://example.org/PLACEHOLDER', true, 207);
suite.server.requests[0].respond(207);
@@ -87,7 +87,7 @@ describe('OC.SetupChecks tests', function() {
});
it('should return no error with the default expected response status code', function(done) {
- var async = OC.SetupChecks.checkWellKnownUrl('/.well-known/caldav', 'http://example.org/PLACEHOLDER', true);
+ var async = OC.SetupChecks.checkWellKnownUrl('PROPFIND', '/.well-known/caldav', 'http://example.org/PLACEHOLDER', true);
suite.server.requests[0].respond(207);
@@ -98,7 +98,7 @@ describe('OC.SetupChecks tests', function() {
});
it('should return no error when no check should be run', function(done) {
- var async = OC.SetupChecks.checkWellKnownUrl('/.well-known/caldav', 'http://example.org/PLACEHOLDER', false);
+ var async = OC.SetupChecks.checkWellKnownUrl('PROPFIND', '/.well-known/caldav', 'http://example.org/PLACEHOLDER', false);
async.done(function( data, s, x ){
expect(data).toEqual([]);
diff --git a/core/routes.php b/core/routes.php
index 9fa378dc1d8..5bc4075315a 100644
--- a/core/routes.php
+++ b/core/routes.php
@@ -89,6 +89,9 @@ $application->registerRoutes($this, [
// Logins for passwordless auth
['name' => 'WebAuthn#startAuthentication', 'url' => 'login/webauthn/start', 'verb' => 'POST'],
['name' => 'WebAuthn#finishAuthentication', 'url' => 'login/webauthn/finish', 'verb' => 'POST'],
+
+ // Well known requests https://tools.ietf.org/html/rfc5785
+ ['name' => 'WellKnown#handle', 'url' => '.well-known/{service}'],
],
'ocs' => [
['root' => '/cloud', 'name' => 'OCS#getCapabilities', 'url' => '/capabilities', 'verb' => 'GET'],
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index c033a237ca4..282d8f402a9 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -359,6 +359,11 @@ return array(
'OCP\\Http\\Client\\IClientService' => $baseDir . '/lib/public/Http/Client/IClientService.php',
'OCP\\Http\\Client\\IResponse' => $baseDir . '/lib/public/Http/Client/IResponse.php',
'OCP\\Http\\Client\\LocalServerException' => $baseDir . '/lib/public/Http/Client/LocalServerException.php',
+ 'OCP\\Http\\WellKnown\\GenericResponse' => $baseDir . '/lib/public/Http/WellKnown/GenericResponse.php',
+ 'OCP\\Http\\WellKnown\\IHandler' => $baseDir . '/lib/public/Http/WellKnown/IHandler.php',
+ 'OCP\\Http\\WellKnown\\IRequestContext' => $baseDir . '/lib/public/Http/WellKnown/IRequestContext.php',
+ 'OCP\\Http\\WellKnown\\IResponse' => $baseDir . '/lib/public/Http/WellKnown/IResponse.php',
+ 'OCP\\Http\\WellKnown\\JrdResponse' => $baseDir . '/lib/public/Http/WellKnown/JrdResponse.php',
'OCP\\IAddressBook' => $baseDir . '/lib/public/IAddressBook.php',
'OCP\\IAppConfig' => $baseDir . '/lib/public/IAppConfig.php',
'OCP\\IAvatar' => $baseDir . '/lib/public/IAvatar.php',
@@ -892,6 +897,7 @@ return array(
'OC\\Core\\Controller\\UserController' => $baseDir . '/core/Controller/UserController.php',
'OC\\Core\\Controller\\WalledGardenController' => $baseDir . '/core/Controller/WalledGardenController.php',
'OC\\Core\\Controller\\WebAuthnController' => $baseDir . '/core/Controller/WebAuthnController.php',
+ 'OC\\Core\\Controller\\WellKnownController' => $baseDir . '/core/Controller/WellKnownController.php',
'OC\\Core\\Controller\\WhatsNewController' => $baseDir . '/core/Controller/WhatsNewController.php',
'OC\\Core\\Controller\\WipeController' => $baseDir . '/core/Controller/WipeController.php',
'OC\\Core\\Data\\LoginFlowV2Credentials' => $baseDir . '/core/Data/LoginFlowV2Credentials.php',
@@ -1136,6 +1142,7 @@ return array(
'OC\\Http\\Client\\ClientService' => $baseDir . '/lib/private/Http/Client/ClientService.php',
'OC\\Http\\Client\\Response' => $baseDir . '/lib/private/Http/Client/Response.php',
'OC\\Http\\CookieHelper' => $baseDir . '/lib/private/Http/CookieHelper.php',
+ 'OC\\Http\\WellKnown\\RequestManager' => $baseDir . '/lib/private/Http/WellKnown/RequestManager.php',
'OC\\InitialStateService' => $baseDir . '/lib/private/InitialStateService.php',
'OC\\Installer' => $baseDir . '/lib/private/Installer.php',
'OC\\IntegrityCheck\\Checker' => $baseDir . '/lib/private/IntegrityCheck/Checker.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index 9afd8d98377..bb11975d089 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -388,6 +388,11 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OCP\\Http\\Client\\IClientService' => __DIR__ . '/../../..' . '/lib/public/Http/Client/IClientService.php',
'OCP\\Http\\Client\\IResponse' => __DIR__ . '/../../..' . '/lib/public/Http/Client/IResponse.php',
'OCP\\Http\\Client\\LocalServerException' => __DIR__ . '/../../..' . '/lib/public/Http/Client/LocalServerException.php',
+ 'OCP\\Http\\WellKnown\\GenericResponse' => __DIR__ . '/../../..' . '/lib/public/Http/WellKnown/GenericResponse.php',
+ 'OCP\\Http\\WellKnown\\IHandler' => __DIR__ . '/../../..' . '/lib/public/Http/WellKnown/IHandler.php',
+ 'OCP\\Http\\WellKnown\\IRequestContext' => __DIR__ . '/../../..' . '/lib/public/Http/WellKnown/IRequestContext.php',
+ 'OCP\\Http\\WellKnown\\IResponse' => __DIR__ . '/../../..' . '/lib/public/Http/WellKnown/IResponse.php',
+ 'OCP\\Http\\WellKnown\\JrdResponse' => __DIR__ . '/../../..' . '/lib/public/Http/WellKnown/JrdResponse.php',
'OCP\\IAddressBook' => __DIR__ . '/../../..' . '/lib/public/IAddressBook.php',
'OCP\\IAppConfig' => __DIR__ . '/../../..' . '/lib/public/IAppConfig.php',
'OCP\\IAvatar' => __DIR__ . '/../../..' . '/lib/public/IAvatar.php',
@@ -921,6 +926,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Core\\Controller\\UserController' => __DIR__ . '/../../..' . '/core/Controller/UserController.php',
'OC\\Core\\Controller\\WalledGardenController' => __DIR__ . '/../../..' . '/core/Controller/WalledGardenController.php',
'OC\\Core\\Controller\\WebAuthnController' => __DIR__ . '/../../..' . '/core/Controller/WebAuthnController.php',
+ 'OC\\Core\\Controller\\WellKnownController' => __DIR__ . '/../../..' . '/core/Controller/WellKnownController.php',
'OC\\Core\\Controller\\WhatsNewController' => __DIR__ . '/../../..' . '/core/Controller/WhatsNewController.php',
'OC\\Core\\Controller\\WipeController' => __DIR__ . '/../../..' . '/core/Controller/WipeController.php',
'OC\\Core\\Data\\LoginFlowV2Credentials' => __DIR__ . '/../../..' . '/core/Data/LoginFlowV2Credentials.php',
@@ -1165,6 +1171,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Http\\Client\\ClientService' => __DIR__ . '/../../..' . '/lib/private/Http/Client/ClientService.php',
'OC\\Http\\Client\\Response' => __DIR__ . '/../../..' . '/lib/private/Http/Client/Response.php',
'OC\\Http\\CookieHelper' => __DIR__ . '/../../..' . '/lib/private/Http/CookieHelper.php',
+ 'OC\\Http\\WellKnown\\RequestManager' => __DIR__ . '/../../..' . '/lib/private/Http/WellKnown/RequestManager.php',
'OC\\InitialStateService' => __DIR__ . '/../../..' . '/lib/private/InitialStateService.php',
'OC\\Installer' => __DIR__ . '/../../..' . '/lib/private/Installer.php',
'OC\\IntegrityCheck\\Checker' => __DIR__ . '/../../..' . '/lib/private/IntegrityCheck/Checker.php',
diff --git a/lib/private/AppFramework/Bootstrap/RegistrationContext.php b/lib/private/AppFramework/Bootstrap/RegistrationContext.php
index 12fca23c51f..3e5d70d6f1c 100644
--- a/lib/private/AppFramework/Bootstrap/RegistrationContext.php
+++ b/lib/private/AppFramework/Bootstrap/RegistrationContext.php
@@ -72,6 +72,9 @@ class RegistrationContext {
/** @var array[] */
private $initialStates = [];
+ /** @var array[] */
+ private $wellKnownHandlers = [];
+
/** @var ILogger */
private $logger;
@@ -174,6 +177,13 @@ class RegistrationContext {
$class
);
}
+
+ public function registerWellKnownHandler(string $class): void {
+ $this->context->registerWellKnown(
+ $this->appId,
+ $class
+ );
+ }
};
}
@@ -260,6 +270,13 @@ class RegistrationContext {
];
}
+ public function registerWellKnown(string $appId, string $class): void {
+ $this->wellKnownHandlers[] = [
+ 'appId' => $appId,
+ 'class' => $class,
+ ];
+ }
+
/**
* @param App[] $apps
*/
@@ -437,4 +454,11 @@ class RegistrationContext {
public function getInitialStates(): array {
return $this->initialStates;
}
+
+ /**
+ * @return array[]
+ */
+ public function getWellKnownHandlers(): array {
+ return $this->wellKnownHandlers;
+ }
}
diff --git a/lib/private/Http/WellKnown/RequestManager.php b/lib/private/Http/WellKnown/RequestManager.php
new file mode 100644
index 00000000000..d17ea5c671b
--- /dev/null
+++ b/lib/private/Http/WellKnown/RequestManager.php
@@ -0,0 +1,124 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+namespace OC\Http\WellKnown;
+
+use OC\AppFramework\Bootstrap\Coordinator;
+use OCP\AppFramework\QueryException;
+use OCP\Http\WellKnown\IHandler;
+use OCP\Http\WellKnown\IRequestContext;
+use OCP\Http\WellKnown\IResponse;
+use OCP\Http\WellKnown\JrdResponse;
+use OCP\IRequest;
+use OCP\IServerContainer;
+use Psr\Log\LoggerInterface;
+use RuntimeException;
+use function array_reduce;
+
+class RequestManager {
+
+ /** @var Coordinator */
+ private $coordinator;
+
+ /** @var IServerContainer */
+ private $container;
+
+ /** @var LoggerInterface */
+ private $logger;
+
+ public function __construct(Coordinator $coordinator,
+ IServerContainer $container,
+ LoggerInterface $logger) {
+ $this->coordinator = $coordinator;
+ $this->container = $container;
+ $this->logger = $logger;
+ }
+
+ public function process(string $service, IRequest $request): ?IResponse {
+ $handlers = $this->loadHandlers();
+ $context = new class($request) implements IRequestContext {
+ /** @var IRequest */
+ private $request;
+
+ public function __construct(IRequest $request) {
+ $this->request = $request;
+ }
+
+ public function getHttpRequest(): IRequest {
+ return $this->request;
+ }
+ };
+
+ $subject = $request->getParam('resource');
+ $initialResponse = new JrdResponse($subject ?? '');
+ $finalResponse = array_reduce($handlers, function (?IResponse $previousResponse, IHandler $handler) use ($context, $service) {
+ return $handler->handle($service, $context, $previousResponse);
+ }, $initialResponse);
+
+ if ($finalResponse instanceof JrdResponse && $finalResponse->isEmpty()) {
+ return null;
+ }
+
+ return $finalResponse;
+ }
+
+ /**
+ * @return IHandler[]
+ */
+ private function loadHandlers(): array {
+ $context = $this->coordinator->getRegistrationContext();
+
+ if ($context === null) {
+ throw new RuntimeException("Well known handlers requested before the apps had been fully registered");
+ }
+
+ $registrations = $context->getWellKnownHandlers();
+ $this->logger->debug(count($registrations) . " well known handlers registered");
+
+ return array_filter(
+ array_map(function (array $registration) {
+ $class = $registration['class'];
+
+ try {
+ $handler = $this->container->get($class);
+
+ if (!($handler) instanceof IHandler) {
+ $this->logger->error("Well known handler $class is invalid");
+
+ return null;
+ }
+
+ return $handler;
+ } catch (QueryException $e) {
+ $this->logger->error("Could not load well known handler $class", [
+ 'exception' => $e,
+ ]);
+
+ return null;
+ }
+ }, $registrations)
+ );
+ }
+}
diff --git a/lib/public/AppFramework/Bootstrap/IRegistrationContext.php b/lib/public/AppFramework/Bootstrap/IRegistrationContext.php
index aaf5ef00bfa..9966d19965a 100644
--- a/lib/public/AppFramework/Bootstrap/IRegistrationContext.php
+++ b/lib/public/AppFramework/Bootstrap/IRegistrationContext.php
@@ -180,4 +180,18 @@ interface IRegistrationContext {
* @since 21.0.0
*/
public function registerInitialStateProvider(string $class): void;
+
+ /**
+ * Register a well known protocol handler
+ *
+ * It is allowed to register more than one handler per app.
+ *
+ * @param string $class
+ * @psalm-param class-string<\OCP\Http\WellKnown\IHandler> $class
+ *
+ * @return void
+ *
+ * @since 21.0.0
+ */
+ public function registerWellKnownHandler(string $class): void;
}
diff --git a/lib/public/Http/WellKnown/GenericResponse.php b/lib/public/Http/WellKnown/GenericResponse.php
new file mode 100644
index 00000000000..6efacc41c00
--- /dev/null
+++ b/lib/public/Http/WellKnown/GenericResponse.php
@@ -0,0 +1,51 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCP\Http\WellKnown;
+
+use OCP\AppFramework\Http\Response;
+
+/**
+ * @since 21.0.0
+ */
+final class GenericResponse implements IResponse {
+
+ /** @var Response */
+ private $response;
+
+ /**
+ * @since 21.0.0
+ */
+ public function __construct(Response $response) {
+ $this->response = $response;
+ }
+
+ /**
+ * @since 21.0.0
+ */
+ public function toHttpResponse(): Response {
+ return $this->response;
+ }
+}
diff --git a/lib/public/Http/WellKnown/IHandler.php b/lib/public/Http/WellKnown/IHandler.php
new file mode 100644
index 00000000000..30fca0da5a2
--- /dev/null
+++ b/lib/public/Http/WellKnown/IHandler.php
@@ -0,0 +1,48 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCP\Http\WellKnown;
+
+/**
+ * Interface for an app handler that reacts to requests to Nextcloud's well
+ * known URLs, e.g. to a WebFinger
+ *
+ * @ref https://tools.ietf.org/html/rfc5785
+ *
+ * @since 21.0.0
+ */
+interface IHandler {
+
+ /**
+ * @param string $service the name of the well known service, e.g. 'webfinger'
+ * @param IRequestContext $context
+ * @param IResponse|null $previousResponse the response of the previous handler, if any
+ *
+ * @return IResponse|null a response object if the request could be handled, null otherwise
+ *
+ * @since 21.0.0
+ */
+ public function handle(string $service, IRequestContext $context, ?IResponse $previousResponse): ?IResponse;
+}
diff --git a/lib/public/Http/WellKnown/IRequestContext.php b/lib/public/Http/WellKnown/IRequestContext.php
new file mode 100644
index 00000000000..d064c467d68
--- /dev/null
+++ b/lib/public/Http/WellKnown/IRequestContext.php
@@ -0,0 +1,46 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCP\Http\WellKnown;
+
+use OCP\IRequest;
+
+/**
+ * The context object for \OCP\Http\IWellKnownHandler::handle
+ *
+ * Objects of this type will transport any optional information, e.g. the request
+ * object through which the app well known handler can obtain URL parameters
+ *
+ * @since 21.0.0
+ */
+interface IRequestContext {
+
+ /**
+ * @return IRequest
+ *
+ * @since 21.0.0
+ */
+ public function getHttpRequest(): IRequest;
+}
diff --git a/lib/public/Http/WellKnown/IResponse.php b/lib/public/Http/WellKnown/IResponse.php
new file mode 100644
index 00000000000..8d7bc307252
--- /dev/null
+++ b/lib/public/Http/WellKnown/IResponse.php
@@ -0,0 +1,39 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCP\Http\WellKnown;
+
+use OCP\AppFramework\Http\Response;
+
+/**
+ * @since 21.0.0
+ */
+interface IResponse {
+
+ /**
+ * @since 21.0.0
+ */
+ public function toHttpResponse(): Response;
+}
diff --git a/lib/public/Http/WellKnown/JrdResponse.php b/lib/public/Http/WellKnown/JrdResponse.php
new file mode 100644
index 00000000000..3a387107e8e
--- /dev/null
+++ b/lib/public/Http/WellKnown/JrdResponse.php
@@ -0,0 +1,170 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCP\Http\WellKnown;
+
+use OCP\AppFramework\Http\JSONResponse;
+use OCP\AppFramework\Http\Response;
+use function array_filter;
+
+/**
+ * A JSON Document Format (JDF) response to a well-known request
+ *
+ * @ref https://tools.ietf.org/html/rfc6415#appendix-A
+ * @ref https://tools.ietf.org/html/rfc7033#section-4.4
+ *
+ * @since 21.0.0
+ */
+final class JrdResponse implements IResponse {
+
+ /** @var string */
+ private $subject;
+
+ /** @var string|null */
+ private $expires;
+
+ /** @var string[] */
+ private $aliases = [];
+
+ /** @var (string|null)[] */
+ private $properties = [];
+
+ /** @var mixed[] */
+ private $links;
+
+ /**
+ * @param string $subject https://tools.ietf.org/html/rfc7033#section-4.4.1
+ *
+ * @since 21.0.0
+ */
+ public function __construct(string $subject) {
+ $this->subject = $subject;
+ }
+
+ /**
+ * @param string $expires
+ *
+ * @return $this
+ *
+ * @since 21.0.0
+ */
+ public function setExpires(string $expires): self {
+ $this->expires = $expires;
+
+ return $this;
+ }
+
+ /**
+ * Add an alias
+ *
+ * @ref https://tools.ietf.org/html/rfc7033#section-4.4.2
+ *
+ * @param string $alias
+ *
+ * @return $this
+ *
+ * @since 21.0.0
+ */
+ public function addAlias(string $alias): self {
+ $this->aliases[] = $alias;
+
+ return $this;
+ }
+
+ /**
+ * Add a property
+ *
+ * @ref https://tools.ietf.org/html/rfc7033#section-4.4.3
+ *
+ * @param string $property
+ * @param string|null $value
+ *
+ * @return $this
+ *
+ * @since 21.0.0
+ */
+ public function addProperty(string $property, ?string $value): self {
+ $this->properties[$property] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Add a link
+ *
+ * @ref https://tools.ietf.org/html/rfc7033#section-8.4
+ *
+ * @param string $rel https://tools.ietf.org/html/rfc7033#section-4.4.4.1
+ * @param string|null $type https://tools.ietf.org/html/rfc7033#section-4.4.4.2
+ * @param string|null $href https://tools.ietf.org/html/rfc7033#section-4.4.4.3
+ * @param string[]|null $titles https://tools.ietf.org/html/rfc7033#section-4.4.4.4
+ * @param string|null $properties https://tools.ietf.org/html/rfc7033#section-4.4.4.5
+ *
+ * @psalm-param array<string,(string|null)>|null $properties https://tools.ietf.org/html/rfc7033#section-4.4.4.5
+ *
+ * @return JrdResponse
+ * @since 21.0.0
+ */
+ public function addLink(string $rel,
+ ?string $type,
+ ?string $href,
+ ?array $titles = [],
+ ?array $properties = []): self {
+ $this->links[] = array_filter([
+ 'rel' => $rel,
+ 'type' => $type,
+ 'href' => $href,
+ 'titles' => $titles,
+ 'properties' => $properties,
+ ]);
+
+ return $this;
+ }
+
+ /**
+ * @since 21.0.0
+ */
+ public function toHttpResponse(): Response {
+ return new JSONResponse(array_filter([
+ 'subject' => $this->subject,
+ 'expires' => $this->expires,
+ 'aliases' => $this->aliases,
+ 'properties' => $this->properties,
+ 'links' => $this->links,
+ ]));
+ }
+
+ /**
+ * Does this response have any data attached to it?
+ *
+ * @since 21.0.0
+ */
+ public function isEmpty(): bool {
+ return $this->expires === null
+ && empty($this->aliases)
+ && empty($this->properties)
+ && empty($this->links);
+ }
+}
diff --git a/tests/Core/Controller/WellKnownControllerTest.php b/tests/Core/Controller/WellKnownControllerTest.php
new file mode 100644
index 00000000000..2a35ed5ac85
--- /dev/null
+++ b/tests/Core/Controller/WellKnownControllerTest.php
@@ -0,0 +1,86 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+namespace Tests\Core\Controller;
+
+use OC\Core\Controller\WellKnownController;
+use OC\Http\WellKnown\RequestManager;
+use OCP\AppFramework\Http\JSONResponse;
+use OCP\Http\WellKnown\IResponse;
+use OCP\IRequest;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+class WellKnownControllerTest extends TestCase {
+
+ /** @var IRequest|MockObject */
+ private $request;
+
+ /** @var RequestManager|MockObject */
+ private $manager;
+
+ /** @var WellKnownController */
+ private $controller;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->request = $this->createMock(IRequest::class);
+ $this->manager = $this->createMock(RequestManager::class);
+
+ $this->controller = new WellKnownController(
+ $this->request,
+ $this->manager,
+ );
+ }
+
+ public function testHandleNotProcessed(): void {
+ $httpResponse = $this->controller->handle("nodeinfo");
+
+ self::assertInstanceOf(JSONResponse::class, $httpResponse);
+ self::assertArrayHasKey('X-NEXTCLOUD-WELL-KNOWN', $httpResponse->getHeaders());
+ }
+
+ public function testHandle(): void {
+ $response = $this->createMock(IResponse::class);
+ $jsonResponse = $this->createMock(JSONResponse::class);
+ $response->expects(self::once())
+ ->method('toHttpResponse')
+ ->willReturn($jsonResponse);
+ $this->manager->expects(self::once())
+ ->method('process')
+ ->with(
+ "nodeinfo",
+ $this->request
+ )->willReturn($response);
+ $jsonResponse->expects(self::once())
+ ->method('addHeader')
+ ->willReturnSelf();
+
+ $httpResponse = $this->controller->handle("nodeinfo");
+
+ self::assertInstanceOf(JSONResponse::class, $httpResponse);
+ }
+}
diff --git a/tests/lib/Http/WellKnown/GenericResponseTest.php b/tests/lib/Http/WellKnown/GenericResponseTest.php
new file mode 100644
index 00000000000..1e84a509c1b
--- /dev/null
+++ b/tests/lib/Http/WellKnown/GenericResponseTest.php
@@ -0,0 +1,40 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+namespace Tests\Http\WellKnown;
+
+use OCP\AppFramework\Http\JSONResponse;
+use OCP\Http\WellKnown\GenericResponse;
+use Test\TestCase;
+
+class GenericResponseTest extends TestCase {
+ public function testToHttpResponse(): void {
+ $httpResponse = $this->createMock(JSONResponse::class);
+
+ $response = new GenericResponse($httpResponse);
+
+ self::assertSame($httpResponse, $response->toHttpResponse());
+ }
+}
diff --git a/tests/lib/Http/WellKnown/JrdResponseTest.php b/tests/lib/Http/WellKnown/JrdResponseTest.php
new file mode 100644
index 00000000000..f540ac1862e
--- /dev/null
+++ b/tests/lib/Http/WellKnown/JrdResponseTest.php
@@ -0,0 +1,108 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+namespace Test\Http\WellKnown;
+
+use OCP\AppFramework\Http\JSONResponse;
+use OCP\Http\WellKnown\JrdResponse;
+use Test\TestCase;
+
+class JrdResponseTest extends TestCase {
+ public function testEmptyToHttpResponse(): void {
+ $response = new JrdResponse("subject");
+ $httpResponse = $response->toHttpResponse();
+
+ self::assertTrue($response->isEmpty());
+ self::assertInstanceOf(JSONResponse::class, $httpResponse);
+ /** @var JSONResponse $httpResponse */
+ self::assertEquals(
+ [
+ 'subject' => 'subject',
+ ],
+ $httpResponse->getData()
+ );
+ }
+
+ public function testComplexToHttpResponse(): void {
+ $response = new JrdResponse("subject");
+ $response->addAlias('alias');
+ $response->addAlias('blias');
+ $response->addProperty('propa', 'a');
+ $response->addProperty('propb', null);
+ $response->setExpires('tomorrow');
+ $response->addLink('rel', null, null);
+ $response->addLink('rel', 'type', null);
+ $response->addLink('rel', 'type', 'href', ['title' => 'titlevalue']);
+ $response->addLink('rel', 'type', 'href', ['title' => 'titlevalue'], ['propx' => 'valx']);
+ $httpResponse = $response->toHttpResponse();
+
+ self::assertFalse($response->isEmpty());
+ self::assertInstanceOf(JSONResponse::class, $httpResponse);
+ /** @var JSONResponse $httpResponse */
+ self::assertEquals(
+ [
+ 'subject' => 'subject',
+ 'aliases' => [
+ 'alias',
+ 'blias',
+ ],
+ 'properties' => [
+ 'propa' => 'a',
+ 'propb' => null,
+ ],
+ 'expires' => 'tomorrow',
+ 'links' => [
+ [
+ 'rel' => 'rel',
+ ],
+ [
+ 'rel' => 'rel',
+ 'type' => 'type',
+ ],
+ [
+ 'rel' => 'rel',
+ 'type' => 'type',
+ 'href' => 'href',
+ 'titles' => [
+ 'title' => 'titlevalue',
+ ],
+ ],
+ [
+ 'rel' => 'rel',
+ 'type' => 'type',
+ 'href' => 'href',
+ 'titles' => [
+ 'title' => 'titlevalue',
+ ],
+ 'properties' => [
+ 'propx' => 'valx',
+ ],
+ ],
+ ]
+ ],
+ $httpResponse->getData()
+ );
+ }
+}
diff --git a/tests/lib/Http/WellKnown/RequestManagerTest.php b/tests/lib/Http/WellKnown/RequestManagerTest.php
new file mode 100644
index 00000000000..b947df49496
--- /dev/null
+++ b/tests/lib/Http/WellKnown/RequestManagerTest.php
@@ -0,0 +1,176 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+namespace Test\Http\WellKnown;
+
+use OC\AppFramework\Bootstrap\Coordinator;
+use OC\AppFramework\Bootstrap\RegistrationContext;
+use OC\Http\WellKnown\RequestManager;
+use OCP\AppFramework\QueryException;
+use OCP\Http\WellKnown\IHandler;
+use OCP\Http\WellKnown\IRequestContext;
+use OCP\Http\WellKnown\IResponse;
+use OCP\Http\WellKnown\JrdResponse;
+use OCP\IRequest;
+use OCP\IServerContainer;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use RuntimeException;
+use Test\TestCase;
+use function get_class;
+
+class RequestManagerTest extends TestCase {
+
+ /** @var Coordinator|MockObject */
+ private $coordinator;
+
+ /** @var IServerContainer|MockObject */
+ private $container;
+
+ /** @var MockObject|LoggerInterface */
+ private $logger;
+
+ /** @var RequestManager */
+ private $manager;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->coordinator = $this->createMock(Coordinator::class);
+ $this->container = $this->createMock(IServerContainer::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+
+ $this->manager = new RequestManager(
+ $this->coordinator,
+ $this->container,
+ $this->logger,
+ );
+ }
+
+ public function testProcessAppsNotRegistered(): void {
+ $request = $this->createMock(IRequest::class);
+ $this->expectException(RuntimeException::class);
+
+ $this->manager->process("webfinger", $request);
+ }
+
+ public function testProcessNoHandlersRegistered(): void {
+ $request = $this->createMock(IRequest::class);
+ $registrationContext = $this->createMock(RegistrationContext::class);
+ $this->coordinator->expects(self::once())
+ ->method('getRegistrationContext')
+ ->willReturn($registrationContext);
+ $registrationContext->expects(self::once())
+ ->method('getWellKnownHandlers')
+ ->willReturn([]);
+
+ $response = $this->manager->process("webfinger", $request);
+
+ self::assertNull($response);
+ }
+
+ public function testProcessHandlerNotLoadable(): void {
+ $request = $this->createMock(IRequest::class);
+ $registrationContext = $this->createMock(RegistrationContext::class);
+ $this->coordinator->expects(self::once())
+ ->method('getRegistrationContext')
+ ->willReturn($registrationContext);
+ $handler = new class {
+ };
+ $registrationContext->expects(self::once())
+ ->method('getWellKnownHandlers')
+ ->willReturn([
+ [
+ 'class' => get_class($handler),
+ ],
+ ]);
+ $this->container->expects(self::once())
+ ->method('get')
+ ->with(get_class($handler))
+ ->willThrowException(new QueryException(""));
+ $this->logger->expects(self::once())
+ ->method('error');
+
+ $response = $this->manager->process("webfinger", $request);
+
+ self::assertNull($response);
+ }
+
+ public function testProcessHandlerOfWrongType(): void {
+ $request = $this->createMock(IRequest::class);
+ $registrationContext = $this->createMock(RegistrationContext::class);
+ $this->coordinator->expects(self::once())
+ ->method('getRegistrationContext')
+ ->willReturn($registrationContext);
+ $handler = new class {
+ };
+ $registrationContext->expects(self::once())
+ ->method('getWellKnownHandlers')
+ ->willReturn([
+ [
+ 'class' => get_class($handler),
+ ],
+ ]);
+ $this->container->expects(self::once())
+ ->method('get')
+ ->with(get_class($handler))
+ ->willReturn($handler);
+ $this->logger->expects(self::once())
+ ->method('error');
+
+ $response = $this->manager->process("webfinger", $request);
+
+ self::assertNull($response);
+ }
+
+ public function testProcess(): void {
+ $request = $this->createMock(IRequest::class);
+ $registrationContext = $this->createMock(RegistrationContext::class);
+ $this->coordinator->expects(self::once())
+ ->method('getRegistrationContext')
+ ->willReturn($registrationContext);
+ $handler = new class implements IHandler {
+ public function handle(string $service, IRequestContext $context, ?IResponse $previousResponse): ?IResponse {
+ return (new JrdResponse($service))->addAlias('alias');
+ }
+ };
+ $registrationContext->expects(self::once())
+ ->method('getWellKnownHandlers')
+ ->willReturn([
+ [
+ 'class' => get_class($handler),
+ ],
+ ]);
+ $this->container->expects(self::once())
+ ->method('get')
+ ->with(get_class($handler))
+ ->willReturn($handler);
+
+ $response = $this->manager->process("webfinger", $request);
+
+ self::assertNotNull($response);
+ self::assertInstanceOf(JrdResponse::class, $response);
+ }
+}