Passed
Push — main ( 16cc9f...4d0cff )
by Daniel
09:49 queued 04:29
created

MercureAuthorization::buildAbsoluteUriTemplate()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 6
c 1
b 0
f 0
nc 4
nop 0
dl 0
loc 11
ccs 0
cts 7
cp 0
crap 20
rs 10
1
<?php
2
3
namespace Silverback\ApiComponentsBundle\Mercure;
4
5
use ApiPlatform\Exception\OperationNotFoundException;
6
use ApiPlatform\Metadata\HttpOperation;
7
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
8
use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface;
9
use Silverback\ApiComponentsBundle\Annotation\Publishable;
10
use Silverback\ApiComponentsBundle\Helper\Publishable\PublishableStatusChecker;
11
use Symfony\Component\HttpFoundation\Cookie;
12
use Symfony\Component\HttpFoundation\RequestStack;
13
use Symfony\Component\Mercure\Authorization;
14
use Symfony\Component\Routing\RequestContext;
15
16
class MercureAuthorization
17
{
18
    public function __construct(
19
        private readonly ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory,
20
        private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory,
21
        private readonly PublishableStatusChecker $publishableStatusChecker,
22
        private readonly RequestContext $requestContext,
23
        private readonly Authorization $mercureAuthorization,
24
        private readonly RequestStack $requestStack,
25
        private readonly string $cookieSameSite = Cookie::SAMESITE_STRICT,
26
        private readonly ?string $hubName = null
27
    ) {
28
    }
29
30
    public function getAuthorizationCookie(): Cookie
31
    {
32
        $subscribeTopics = $this->getSubscribeTopics();
33
        // Todo: await merge of https://github.com/symfony/mercure/pull/93 to remove ability to publish any updates and set to  null
34
        // May also be able to await a mercure bundle update to set the cookie samesite in mercure configs
35
        $cookie = $this->mercureAuthorization->createCookie($this->requestStack->getCurrentRequest(), $subscribeTopics, [], [], $this->hubName);
0 ignored issues
show
Bug introduced by
It seems like $this->requestStack->getCurrentRequest() can also be of type null; however, parameter $request of Symfony\Component\Mercur...ization::createCookie() does only seem to accept Symfony\Component\HttpFoundation\Request, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

35
        $cookie = $this->mercureAuthorization->createCookie(/** @scrutinizer ignore-type */ $this->requestStack->getCurrentRequest(), $subscribeTopics, [], [], $this->hubName);
Loading history...
36
        return $cookie
37
            ->withSameSite($this->cookieSameSite)
38
            ->withExpires(time() + (10 * 365 * 24 * 60 * 60));
39
    }
40
41
    public function getClearAuthorizationCookie(): Cookie
42
    {
43
        return $this->getAuthorizationCookie()->withExpires(1);
44
    }
45
46
    public function getSubscribeTopics(): array
47
    {
48
        $subscribeIris = [];
49
        foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) {
50
            if ($resourceIris = $this->getSubscribeIrisForResource($resourceClass)) {
51
                $subscribeIris[] = $resourceIris;
52
            }
53
        }
54
        return array_merge([], ...$subscribeIris);
55
    }
56
57
    private function getSubscribeIrisForResource(string $resourceClass): ?array
58
    {
59
        $operation = $this->getMercureResourceOperation($resourceClass);
60
        if (!$operation) {
61
            return null;
62
        }
63
64
        $refl = new \ReflectionClass($operation->getClass());
65
        $isPublishable = \count($refl->getAttributes(Publishable::class));
66
67
        $uriTemplate = $this->buildAbsoluteUriTemplate() . $operation->getRoutePrefix() . $operation->getUriTemplate();
68
        $subscribeIris = [$uriTemplate];
69
70
        if (!$isPublishable) {
71
            return $subscribeIris;
72
        }
73
74
        // Note that `?draft=1` is also hard coded into the PublishableIriConverter, probably make this configurable somewhere
75
        if ($this->publishableStatusChecker->isGranted($operation->getClass())) {
76
            $subscribeIris[] = $uriTemplate . '?draft=1';
77
        }
78
79
        return $subscribeIris;
80
    }
81
82
    private function getMercureResourceOperation(string $resourceClass): ?HttpOperation
83
    {
84
        $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass);
85
86
        try {
87
            $operation = $resourceMetadataCollection->getOperation(forceCollection: false, httpOperation: true);
88
        } catch (OperationNotFoundException $e) {
89
            return null;
90
        }
91
92
        if (!$operation instanceof HttpOperation) {
93
            return null;
94
        }
95
96
        $mercure = $operation->getMercure();
97
98
        if (!$mercure) {
99
            return null;
100
        }
101
102
        return $operation;
103
    }
104
105
    /**
106
     * Mercure subscribe iris should be absolute
107
     * this code can also be found in Symfony's URL Generator
108
     * but as we work without a symfony route here (and we would not want to do this as its not spec-compliant)
109
     * we do it by hand.
110
     */
111
    private function buildAbsoluteUriTemplate(): string
112
    {
113
        $scheme = $this->requestContext->getScheme();
114
        $host = $this->requestContext->getHost();
115
        $port = $this->requestContext->isSecure() ? $this->requestContext->getHttpsPort() : $this->requestContext->getHttpPort();
116
117
        if (80 !== $port || 443 !== $port) {
118
            return sprintf('%s://%s:%d', $scheme, $host, $port);
119
        }
120
121
        return sprintf('%s://%s', $scheme, $host);
122
    }
123
}
124