Passed
Pull Request — master (#17)
by BENOIT
01:49
created

SubscriptionsController   A

Complexity

Total Complexity 7

Size/Duplication

Total Lines 68
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 39
c 1
b 0
f 0
dl 0
loc 68
rs 10
wmc 7

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __invoke() 0 51 5
A matchRequest() 0 3 1
A __construct() 0 4 1
1
<?php
2
3
namespace BenTools\MercurePHP\Controller;
4
5
use BenTools\MercurePHP\Exception\Http\AccessDeniedHttpException;
6
use BenTools\MercurePHP\Hub\Hub;
7
use BenTools\MercurePHP\Security\Authenticator;
8
use BenTools\MercurePHP\Security\TopicMatcher;
9
use Psr\Http\Message\RequestInterface;
10
use Psr\Http\Message\ResponseInterface;
11
use Psr\Http\Message\ServerRequestInterface;
12
use React\Http\Message\Response;
13
use React\Stream\ThroughStream;
14
15
use function BenTools\MercurePHP\nullify;
16
17
final class SubscriptionsController extends AbstractController
18
{
19
    private const PATH = '/.well-known/mercure/subscriptions';
20
    private Hub $hub;
21
    private Authenticator $authenticator;
22
23
    public function __construct(Hub $hub, Authenticator $authenticator)
24
    {
25
        $this->hub = $hub;
26
        $this->authenticator = $authenticator;
27
    }
28
29
    public function __invoke(ServerRequestInterface $request): ResponseInterface
30
    {
31
        if (!\in_array($request->getMethod(), ['GET', 'HEAD', 'OPTIONS'])) {
32
            return new Response(405);
33
        }
34
35
        $path = $request->getUri()->getPath();
36
        $filters = \explode('/', \trim(\strtr($path, [self::PATH => '']), '/'), 2);
37
        $subscriber = nullify($filters[0]);
38
        $topic = nullify($filters[1] ?? null);
39
40
        $token = $this->authenticator->authenticate($request);
41
42
        if (null === $token) {
43
            throw new AccessDeniedHttpException('You must be authenticated to access the Subscription API.');
44
        }
45
46
        $claim = (array) $token->getClaim('mercure');
47
        $allowedTopics = $claim['subscribe'] ?? [];
48
        $deniedTopics = $claim['subscribe_exclude'] ?? [];
49
        $matchAllowedTopics = TopicMatcher::matchesTopicSelectors($path, $allowedTopics);
50
        $matchDeniedTopics = TopicMatcher::matchesTopicSelectors($path, $deniedTopics);
51
        if (!$matchAllowedTopics || $matchDeniedTopics) {
52
            throw new AccessDeniedHttpException('You are not authorized to display these subscriptions.');
53
        }
54
55
        $stream = new ThroughStream();
56
        $this->hub->hook(
57
            function () use ($stream, $path, $subscriber, $topic) {
58
                $this->hub->getActiveSubscriptions($subscriber, $topic)
59
                    ->then(
60
                        function (iterable $subscriptions) use ($stream, $path) {
61
                            $result = [
62
                                '@context' => 'https://mercure.rocks/',
63
                                'id' => $path,
64
                                'type' => 'Subscriptions',
65
                                'subscriptions' => \iterable_to_array($subscriptions),
66
                            ];
67
                            $stream->write(\json_encode($result, \JSON_THROW_ON_ERROR));
68
                            $stream->end();
69
                            $stream->close();
70
                        }
71
                    );
72
            }
73
        );
74
75
        $headers = [
76
            'Content-Type' => 'application/ld+json',
77
        ];
78
79
        return new Response(200, $headers, $stream);
80
    }
81
82
    public function matchRequest(RequestInterface $request): bool
83
    {
84
        return 0 === \strpos($request->getUri()->getPath(), self::PATH);
85
    }
86
}
87