Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
2 changes: 1 addition & 1 deletion public/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
32 changes: 32 additions & 0 deletions src/Configuration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Webubbub;

/**
* @phpstan-type ConfigurationApplication array{
* 'allowed_topic_origins': string,
* }
*
* @author Marien Fressinaud <dev@marienfressinaud.fr>
* @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']));
}
}
3 changes: 1 addition & 2 deletions src/controllers/Home.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
]);
}

Expand Down
6 changes: 6 additions & 0 deletions src/controllers/Requests.php
Original file line number Diff line number Diff line change
Expand Up @@ -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', [
Expand Down
3 changes: 3 additions & 0 deletions src/jobs/ProcessContents.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ public function performFetch(): void
\Minz\Log::warning(
"content #{$content->id}: {$http_code} HTTP code is not successful"
);

$content->remove();

continue;
}

Expand Down
9 changes: 9 additions & 0 deletions src/models/Content.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Minz\Database;
use Minz\Validable;
use Webubbub\utils;

/**
* Represent a content created by publishers, it is delivered to subscribers.
Expand Down Expand Up @@ -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.
Expand Down
22 changes: 2 additions & 20 deletions src/models/Subscription.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Minz\Database;
use Minz\Validable;
use Webubbub\utils;

/**
* Represent the subscription of a subscriber (callback) to a topic.
Expand Down Expand Up @@ -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);
}

/**
Expand Down
29 changes: 29 additions & 0 deletions src/utils/AllowedOriginHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace Webubbub\utils;

/**
* @author Marien Fressinaud <dev@marienfressinaud.fr>
* @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;
}
}
2 changes: 1 addition & 1 deletion tests/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@

include $app_path . '/vendor/autoload.php';

\Minz\Configuration::load('test', $app_path);
\Webubbub\Configuration::load('test', $app_path);
16 changes: 16 additions & 0 deletions tests/cli/RequestsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
6 changes: 1 addition & 5 deletions tests/jobs/ProcessContentsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 8 additions & 8 deletions tests/jobs/ProcessSubscriptionsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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',
Expand All @@ -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',
Expand All @@ -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',
Expand All @@ -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
Expand Down