Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Mercure] Add the component #28877

Closed
wants to merge 13 commits into from
Expand Up @@ -424,11 +424,12 @@
<xsd:element name="hub" type="mercure_hub" minOccurs="0" maxOccurs="unbounded" />
dunglas marked this conversation as resolved.
Show resolved Hide resolved
</xsd:sequence>
<xsd:attribute name="default-hub" type="xsd:string" />
dunglas marked this conversation as resolved.
Show resolved Hide resolved
<xsd:attribute name="enabled" type="xsd:boolean" />
</xsd:complexType>

<xsd:complexType name="mercure_hub">
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="url" type="xsd:string" use="required" />
<xsd:attribute name="name" type="xsd:string" use="required" />
<xsd:attribute name="url" type="xsd:string" />
<xsd:attribute name="jwt" type="xsd:string" />
<xsd:attribute name="jwt_provider" type="xsd:string" />
<xsd:attribute name="bus" type="xsd:string" />
Expand Down
Expand Up @@ -300,6 +300,10 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor
'default_bus' => null,
'buses' => array('messenger.bus.default' => array('default_middleware' => true, 'middleware' => array())),
),
'mercure' => array(
'enabled' => !class_exists(FullStack::class) && class_exists(Update::class),
'hubs' => array(),
),
);
}
}
35 changes: 29 additions & 6 deletions src/Symfony/Component/Mercure/Publisher.php
Expand Up @@ -22,13 +22,17 @@
*/
final class Publisher
{
private $publishEndpoint;
private $hubUrl;
private $jwtProvider;
private $httpClient;

public function __construct(string $publishEndpoint, callable $jwtProvider, callable $httpClient = null)
/**
* @param callable(): string $jwtProvider
* @param null|callable(string, callable(): string, string): string $httpClient
*/
public function __construct(string $hubUrl, callable $jwtProvider, callable $httpClient = null)
{
$this->publishEndpoint = $publishEndpoint;
$this->hubUrl = $hubUrl;
$this->jwtProvider = $jwtProvider;
$this->httpClient = $httpClient ?? array($this, 'publish');
}
Expand All @@ -44,7 +48,10 @@ public function __invoke(Update $update): string
'retry' => $update->getRetry(),
);

return ($this->httpClient)($this->publishEndpoint, ($this->jwtProvider)(), $this->buildQuery($postData));
$jwt = ($this->jwtProvider)();
$this->validateJwt($jwt);

return ($this->httpClient)($this->hubUrl, $jwt, $this->buildQuery($postData));
}

/**
Expand Down Expand Up @@ -80,16 +87,32 @@ private function encode($key, $value): string

private function publish(string $url, string $jwt, string $postData): string
{
$result = @file_get_contents($this->publishEndpoint, false, stream_context_create(array('http' => array(
$result = @file_get_contents($this->hubUrl, false, stream_context_create(array('http' => array(
'method' => 'POST',
'header' => "Content-type: application/x-www-form-urlencoded\r\nAuthorization: Bearer $jwt",
dunglas marked this conversation as resolved.
Show resolved Hide resolved
'content' => $postData,
))));

if (false === $result) {
throw new \RuntimeException(sprintf('Unable to publish the update to the Mercure hub: %s', error_get_last()));
throw new \RuntimeException(sprintf('Unable to publish the update to the Mercure hub: %s', error_get_last()['message'] ?? 'unknown error'));
}

return $result;
}

/**
* Regex ported from Windows Azure Active Directory IdentityModel Extensions for .Net.
dunglas marked this conversation as resolved.
Show resolved Hide resolved
*
* @throws \InvalidArgumentException
*
* @license MIT
* @copyright Copyright (c) Microsoft Corporation
* @see https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/6e7a53e241e4566998d3bf365f03acd0da699a31/src/Microsoft.IdentityModel.JsonWebTokens/JwtConstants.cs#L58
*/
private function validateJwt(string $jwt): void
{
if (!preg_match('/^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]*$/', $jwt)) {
throw new \InvalidArgumentException('The provided JWT is not valid');
}
}
}
14 changes: 14 additions & 0 deletions src/Symfony/Component/Mercure/Tests/PublisherTest.php
Expand Up @@ -45,4 +45,18 @@ public function testPublish()

$this->assertSame('id', $id);
}

/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The provided JWT is not valid
*/
public function testInvalidJwt()
{
$jwtProvider = function () {
return "invalid\r\njwt";
};

$publisher = new Publisher(self::URL, $jwtProvider);
$publisher(new Update('https://demo.mercure.rocks/demo/books/1.jsonld', 'Hi from Symfony!'));
}
}