diff --git a/cli b/cli index 214139a..a023fbd 100755 --- a/cli +++ b/cli @@ -14,7 +14,7 @@ $app_path = __DIR__; include $app_path . '/vendor/autoload.php'; -\Minz\Configuration::load('dotenv', $app_path); +\Webubbub\Configuration::load('dotenv', $app_path); $request = \Minz\Request::initFromCli($argv); diff --git a/public/index.php b/public/index.php index c4f3513..5d4b218 100644 --- a/public/index.php +++ b/public/index.php @@ -11,7 +11,7 @@ include $app_path . '/vendor/autoload.php'; -\Minz\Configuration::load('dotenv', $app_path); +\Webubbub\Configuration::load('dotenv', $app_path); try { $application = new \Webubbub\Application(); diff --git a/src/Configuration.php b/src/Configuration.php new file mode 100644 index 0000000..e588015 --- /dev/null +++ b/src/Configuration.php @@ -0,0 +1,32 @@ + + * @license http://www.gnu.org/licenses/agpl-3.0.en.html AGPL + */ +class Configuration extends \Minz\Configuration +{ + /** + * @var ConfigurationApplication + */ + public static array $application; + + public static function isPublicHub(): bool + { + return self::$application['allowed_topic_origins'] === ''; + } + + /** + * @return string[] + */ + public static function allowedTopicOrigins(): array + { + return array_map('trim', explode(',', self::$application['allowed_topic_origins'])); + } +} diff --git a/src/controllers/Home.php b/src/controllers/Home.php index 3bbe568..42e23ed 100644 --- a/src/controllers/Home.php +++ b/src/controllers/Home.php @@ -16,9 +16,8 @@ class Home */ public function index(Request $request): Response { - $is_public_hub = \Minz\Configuration::$application['allowed_topic_origins'] === ''; return Response::ok('home/index.phtml', [ - 'is_public_hub' => $is_public_hub, + 'is_public_hub' => \Webubbub\Configuration::isPublicHub(), ]); } diff --git a/src/controllers/Requests.php b/src/controllers/Requests.php index cf33068..9e61972 100644 --- a/src/controllers/Requests.php +++ b/src/controllers/Requests.php @@ -187,6 +187,12 @@ public function publish(Request $request): Response $content = new models\Content($url); + if (!$content->isAllowed()) { + return Response::badRequest('requests/error.txt', [ + 'errors' => ["url \"{$url}\" is not authorized"], + ]); + } + $errors = $content->validate(); if ($errors) { return Response::badRequest('requests/error.txt', [ diff --git a/src/jobs/ProcessContents.php b/src/jobs/ProcessContents.php index f997eae..2a6300b 100644 --- a/src/jobs/ProcessContents.php +++ b/src/jobs/ProcessContents.php @@ -55,6 +55,9 @@ public function performFetch(): void \Minz\Log::warning( "content #{$content->id}: {$http_code} HTTP code is not successful" ); + + $content->remove(); + continue; } diff --git a/src/models/Content.php b/src/models/Content.php index af7220d..64ec1c5 100644 --- a/src/models/Content.php +++ b/src/models/Content.php @@ -4,6 +4,7 @@ use Minz\Database; use Minz\Validable; +use Webubbub\utils; /** * Represent a content created by publishers, it is delivered to subscribers. @@ -85,6 +86,14 @@ public function deliver(): void $this->status = 'delivered'; } + /** + * Return wheter a content is allowed on the hub or not. + */ + public function isAllowed(): bool + { + return utils\AllowedOriginHelper::isOriginAllowed($this->url); + } + /** * Delete the Contents that can be deleted and return the number of * deletions. diff --git a/src/models/Subscription.php b/src/models/Subscription.php index d562bbc..3617018 100644 --- a/src/models/Subscription.php +++ b/src/models/Subscription.php @@ -4,6 +4,7 @@ use Minz\Database; use Minz\Validable; +use Webubbub\utils; /** * Represent the subscription of a subscriber (callback) to a topic. @@ -138,26 +139,7 @@ public function __construct( */ public function isAllowed(): bool { - $allowed_origins = \Minz\Configuration::$application['allowed_topic_origins']; - assert(is_string($allowed_origins)); - - if ($allowed_origins === '') { - // Empty value means open hub - return true; - } - - $allowed_origins = explode(',', $allowed_origins); - - foreach ($allowed_origins as $allowed_origin) { - $allowed_origin = trim($allowed_origin); - - $origin_length = strlen($allowed_origin); - if (substr($this->topic, 0, $origin_length) === $allowed_origin) { - return true; - } - } - - return false; + return utils\AllowedOriginHelper::isOriginAllowed($this->topic); } /** diff --git a/src/utils/AllowedOriginHelper.php b/src/utils/AllowedOriginHelper.php new file mode 100644 index 0000000..05a8d5b --- /dev/null +++ b/src/utils/AllowedOriginHelper.php @@ -0,0 +1,29 @@ + + * @license http://www.gnu.org/licenses/agpl-3.0.en.html AGPL + */ +class AllowedOriginHelper +{ + public static function isOriginAllowed(string $origin): bool + { + if (\Webubbub\Configuration::isPublicHub()) { + return true; + } + + $allowed_topic_origins = explode(',', \Webubbub\Configuration::$application['allowed_topic_origins']); + + foreach ($allowed_topic_origins as $allowed_topic_origin) { + $allowed_topic_origin = trim($allowed_topic_origin); + + if (str_starts_with($origin, $allowed_topic_origin)) { + return true; + } + } + + return false; + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 9c7e7ba..4eb6b2d 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -6,4 +6,4 @@ include $app_path . '/vendor/autoload.php'; -\Minz\Configuration::load('test', $app_path); +\Webubbub\Configuration::load('test', $app_path); diff --git a/tests/cli/RequestsTest.php b/tests/cli/RequestsTest.php index 6da5071..508c945 100644 --- a/tests/cli/RequestsTest.php +++ b/tests/cli/RequestsTest.php @@ -251,6 +251,22 @@ public function testPublishWithSameUrlAndFetchedStatus(): void $this->assertSame(2, models\Content::count()); } + public function testPublishFailsIfUrlIsNotAuthorized(): void + { + \Webubbub\Configuration::$application['allowed_topic_origins'] = 'https://allowed.1.com,https://allowed.2.com'; + + $response = $this->appRun('CLI', '/requests/publish', [ + 'hub_url' => 'https://not.allowed.com', + ]); + + \Webubbub\Configuration::$application['allowed_topic_origins'] = ''; + + $this->assertResponseCode($response, 400); + $this->assertResponseContains($response, 'url "https://not.allowed.com" is not authorized'); + $this->assertResponseHeaders($response, ['Content-Type' => 'text/plain']); + $this->assertSame(0, models\Content::count()); + } + #[\PHPUnit\Framework\Attributes\DataProvider('invalidUrlProvider')] public function testPublishFailsIfUrlIsInvalid(string $invalid_url): void { diff --git a/tests/jobs/ProcessContentsTest.php b/tests/jobs/ProcessContentsTest.php index d7af2fe..96ae1bc 100644 --- a/tests/jobs/ProcessContentsTest.php +++ b/tests/jobs/ProcessContentsTest.php @@ -214,11 +214,7 @@ public function testPerformWithErrorHttpCode(): void $processor = new ProcessContents(); $processor->perform(); - $content = $content->reload(); - $this->assertSame('new', $content->status); - $this->assertNull($content->content); - $this->assertNull($content->type); - $this->assertNull($content->links); + $this->assertFalse(models\Content::exists($content->id)); } public function testPerformWithDeliveryTryAtInFuture(): void diff --git a/tests/jobs/ProcessSubscriptionsTest.php b/tests/jobs/ProcessSubscriptionsTest.php index 1bbdd63..09eada4 100644 --- a/tests/jobs/ProcessSubscriptionsTest.php +++ b/tests/jobs/ProcessSubscriptionsTest.php @@ -45,7 +45,7 @@ public function testPerform(): void public function testPerformWithAllowedTopic(): void { - \Minz\Configuration::$application['allowed_topic_origins'] = 'https://allowed.1.com,https://allowed.2.com'; + \Webubbub\Configuration::$application['allowed_topic_origins'] = 'https://allowed.1.com,https://allowed.2.com'; $subscription = SubscriptionFactory::create([ 'status' => 'new', 'pending_request' => 'subscribe', @@ -58,12 +58,12 @@ public function testPerformWithAllowedTopic(): void $subscription = $subscription->reload(); $this->assertSame('verified', $subscription->status); - \Minz\Configuration::$application['allowed_topic_origins'] = ''; + \Webubbub\Configuration::$application['allowed_topic_origins'] = ''; } public function testPerformWithNotAllowedTopic(): void { - \Minz\Configuration::$application['allowed_topic_origins'] = 'https://allowed.1.com,https://allowed.2.com'; + \Webubbub\Configuration::$application['allowed_topic_origins'] = 'https://allowed.1.com,https://allowed.2.com'; $subscription = SubscriptionFactory::create([ 'status' => 'new', 'pending_request' => 'subscribe', @@ -75,13 +75,13 @@ public function testPerformWithNotAllowedTopic(): void $this->assertFalse(models\Subscription::exists($subscription->id)); - \Minz\Configuration::$application['allowed_topic_origins'] = ''; + \Webubbub\Configuration::$application['allowed_topic_origins'] = ''; } #[\PHPUnit\Framework\Attributes\DataProvider('failingHttpCodeProvider')] public function testPerformWithNotAllowedTopicAndRecentSubscriptionAndFailingResponse(int $http_code): void { - \Minz\Configuration::$application['allowed_topic_origins'] = 'https://allowed.1.com,https://allowed.2.com'; + \Webubbub\Configuration::$application['allowed_topic_origins'] = 'https://allowed.1.com,https://allowed.2.com'; $subscription = SubscriptionFactory::create([ 'status' => 'new', 'pending_request' => 'subscribe', @@ -96,13 +96,13 @@ public function testPerformWithNotAllowedTopicAndRecentSubscriptionAndFailingRes $subscription = $subscription->reload(); $this->assertSame('new', $subscription->status); - \Minz\Configuration::$application['allowed_topic_origins'] = ''; + \Webubbub\Configuration::$application['allowed_topic_origins'] = ''; } #[\PHPUnit\Framework\Attributes\DataProvider('failingHttpCodeProvider')] public function testPerformWithNotAllowedTopicAndOldSubscriptionAndFailingResponse(int $http_code): void { - \Minz\Configuration::$application['allowed_topic_origins'] = 'https://allowed.1.com,https://allowed.2.com'; + \Webubbub\Configuration::$application['allowed_topic_origins'] = 'https://allowed.1.com,https://allowed.2.com'; $subscription = SubscriptionFactory::create([ 'status' => 'new', 'pending_request' => 'subscribe', @@ -116,7 +116,7 @@ public function testPerformWithNotAllowedTopicAndOldSubscriptionAndFailingRespon $this->assertFalse(models\Subscription::exists($subscription->id)); - \Minz\Configuration::$application['allowed_topic_origins'] = ''; + \Webubbub\Configuration::$application['allowed_topic_origins'] = ''; } public function testPerformWithSubscribePendingRequest(): void