Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
78.57% covered (warning)
78.57%
22 / 28
25.00% covered (danger)
25.00%
1 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
CsrfTokenContextBuilder
78.57% covered (warning)
78.57%
22 / 28
25.00% covered (danger)
25.00%
1 / 4
17.21
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 process
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
9.02
 createToken
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 validateToken
55.56% covered (warning)
55.56%
5 / 9
0.00% covered (danger)
0.00%
0 / 1
5.40
1<?php
2namespace Apie\ApieBundle\ContextBuilders;
3
4use Apie\Core\Context\ApieContext;
5use Apie\Core\ContextBuilders\ContextBuilderInterface;
6use Apie\Core\ContextConstants;
7use Apie\Core\Exceptions\InvalidCsrfTokenException;
8use Apie\Core\Session\CsrfTokenProvider;
9use Apie\Core\Session\FakeTokenProvider;
10use Apie\Serializer\Interfaces\DecoderInterface;
11use Psr\Http\Message\ServerRequestInterface;
12use Symfony\Component\Security\Csrf\CsrfToken;
13use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
14
15class CsrfTokenContextBuilder implements ContextBuilderInterface, CsrfTokenProvider
16{
17    private string $tokenName = 'apie,apie';
18
19    /** @var array<string, bool> */
20    private array $alreadyChecked = [];
21
22    public function __construct(private readonly ?CsrfTokenManagerInterface $csrfTokenManager = null)
23    {
24    }
25
26    public function process(ApieContext $context): ApieContext
27    {
28        $this->tokenName = $context->hasContext(ContextConstants::RESOURCE_NAME)
29            ? $context->getContext(ContextConstants::RESOURCE_NAME)
30            : 'apie';
31        $this->tokenName .= ',';
32        $this->tokenName .=  $context->hasContext(ContextConstants::APIE_ACTION)
33            ? $context->getContext(ContextConstants::APIE_ACTION)
34            : 'apie';
35        if ($context->hasContext(DecoderInterface::class)
36            && $context->hasContext(ContextConstants::RAW_CONTENTS)
37            && $context->hasContext(ServerRequestInterface::class)
38            && !$context->getContext(ServerRequestInterface::class)->hasHeader('x-no-crsf')
39            && $context->getContext(DecoderInterface::class)?->requiresCsrf()) {
40            $csrf = $context->getContext(ContextConstants::RAW_CONTENTS)['_csrf'] ?? '';
41            $this->validateToken($csrf);
42        }
43
44        if (null === $this->csrfTokenManager) {
45            return $context->withContext(CsrfTokenProvider::class, new FakeTokenProvider());
46        }
47        
48        return $context->withContext(CsrfTokenProvider::class, $this);
49    }
50
51    public function createToken(): string
52    {
53        return $this->csrfTokenManager->getToken($this->tokenName)->getValue();
54    }
55
56    public function validateToken(string $token): void
57    {
58        if (!empty($this->alreadyChecked[$token])) {
59            return;
60        }
61        if ($this->csrfTokenManager) {
62            $csrfToken = new CsrfToken($this->tokenName, $token);
63            if (!$this->csrfTokenManager->isTokenValid($csrfToken)) {
64                throw new InvalidCsrfTokenException();
65            }
66            $this->csrfTokenManager->removeToken($this->tokenName);
67        } else {
68            (new FakeTokenProvider())->validateToken($token);
69        }
70        $this->alreadyChecked[$token] = true;
71    }
72}