Test Failed
Push — master ( 61801d...f3c992 )
by Daniel
11:05
created

JWTManager::create()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 3
eloc 7
c 5
b 0
f 0
nc 4
nop 2
dl 0
loc 15
ccs 0
cts 6
cp 0
crap 12
rs 10
1
<?php
2
3
/*
4
 * This file is part of the Silverback API Components Bundle Project
5
 *
6
 * (c) Daniel West <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Silverback\ApiComponentsBundle\Security;
15
16
use Doctrine\ORM\OptimisticLockException;
17
use Lexik\Bundle\JWTAuthenticationBundle\Exception\InvalidPayloadException;
18
use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTDecodeFailureException;
19
use Lexik\Bundle\JWTAuthenticationBundle\Exception\UserNotFoundException;
20
use Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Token\PreAuthenticationJWTUserToken;
21
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
22
use Silverback\ApiComponentsBundle\Event\JWTRefreshedEvent;
23
use Silverback\ApiComponentsBundle\RefreshToken\RefreshToken;
24
use Silverback\ApiComponentsBundle\RefreshToken\Storage\RefreshTokenStorageInterface;
25
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
26
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
27
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
28
use Symfony\Component\Security\Core\User\UserInterface;
29
use Symfony\Component\Security\Core\User\UserProviderInterface;
30
31
/**
32
 * @author Vincent Chalamon <[email protected]>
33
 */
34
final class JWTManager implements JWTTokenManagerInterface
35
{
36
    private JWTTokenManagerInterface $decorated;
37
    private EventDispatcherInterface $dispatcher;
38
    private UserProviderInterface $userProvider;
39
    private RefreshTokenStorageInterface $storage;
40
41
    public function __construct(JWTTokenManagerInterface $decorated, EventDispatcherInterface $dispatcher, UserProviderInterface $userProvider, RefreshTokenStorageInterface $storage)
42
    {
43
        $this->decorated = $decorated;
44
        $this->dispatcher = $dispatcher;
45
        $this->userProvider = $userProvider;
46
        $this->storage = $storage;
47
    }
48
49
    /**
50
     * {@inheritdoc}
51
     */
52
    public function create(UserInterface $user, ?RefreshToken $token = null): string
53
    {
54
        try {
55
            if ($token) {
56
                $this->storage->createAndExpire($user, $token);
57
            } else {
58
                $this->storage->createAndExpireAll($user);
59
            }
60
        } catch (OptimisticLockException $exception) {
61
            // do nothing, we have already modified the refresh token.
62
            // we can continue to generate a jwt token, won't make any difference
63
            // if the user has a new one of these...
64
        }
65
66
        return $this->decorated->create($user);
67
    }
68
69
    /**
70
     * {@inheritdoc}
71
     */
72
    public function decode(TokenInterface $token): array|false
73
    {
74
        // parse will be used for old symfony where PreAuthenticationJWTUserToken exists
75
        try {
76
            return $this->decorated->decode($token);
77
        } catch (JWTDecodeFailureException $exception) {
78
            $jwtUserToken = $this->handleJWTDecodeFailureException($exception);
79
80
            return $this->decorated->decode(new PreAuthenticationJWTUserToken($jwtUserToken));
81
        }
82
    }
83
84
    public function parse(string $token): array
85
    {
86
        try {
87
            return $this->decorated->parse($token);
88
        } catch (JWTDecodeFailureException $exception) {
89
            $jwtUserToken = $this->handleJWTDecodeFailureException($exception);
90
91
            return $this->decorated->parse($jwtUserToken);
92
        }
93
    }
94
95
    private function handleJWTDecodeFailureException(JWTDecodeFailureException $exception): string
96
    {
97
        if (JWTDecodeFailureException::EXPIRED_TOKEN !== $exception->getReason()) {
98
            throw $exception;
99
        }
100
101
        $payload = $exception->getPayload();
102
        $idClaim = $this->getUserIdClaim();
103
104
        if (!isset($payload[$idClaim])) {
105
            throw new InvalidPayloadException($idClaim);
106
        }
107
108
        $identity = $payload[$idClaim];
109
        try {
110
            $user = $this->userProvider->loadUserByIdentifier($identity);
0 ignored issues
show
Bug introduced by
The method loadUserByIdentifier() does not exist on Symfony\Component\Securi...r\UserProviderInterface. Did you maybe mean loadUserByUsername()? ( Ignorable by Annotation )

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

110
            /** @scrutinizer ignore-call */ 
111
            $user = $this->userProvider->loadUserByIdentifier($identity);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
111
        } catch (UsernameNotFoundException $e) {
112
            throw new UserNotFoundException($idClaim, $identity);
113
        }
114
115
        $refreshToken = $this->resolveCurrentRefreshToken($user);
116
        if (!$refreshToken) {
117
            throw $exception;
118
        }
119
120
        $accessToken = $this->create($user, $refreshToken);
121
122
        $this->dispatcher->dispatch(new JWTRefreshedEvent($accessToken));
123
124
        return $accessToken;
125
    }
126
127
    private function resolveCurrentRefreshToken(UserInterface $user): ?RefreshToken
128
    {
129
        // the refresh token could have just ben expired and is being refreshed by another request
130
        $refreshToken = $this->storage->findOneByUser($user);
131
        if (!$refreshToken || $refreshToken->isExpired()) {
132
            return null;
133
        }
134
135
        return $refreshToken;
136
    }
137
138
    /**
139
     * {@inheritdoc}
140
     */
141
    public function setUserIdentityField($field)
142
    {
143
        return $this->decorated->setUserIdentityField($field);
144
    }
145
146
    /**
147
     * {@inheritdoc}
148
     */
149
    public function getUserIdentityField(): string
150
    {
151
        return $this->decorated->getUserIdentityField();
152
    }
153
154
    /**
155
     * {@inheritdoc}
156
     */
157
    public function getUserIdClaim(): string
158
    {
159
        return $this->decorated->getUserIdClaim();
160
    }
161
162
    /**
163
     * {@inheritdoc}
164
     */
165
    public function createFromPayload(UserInterface $user, array $payload): string
166
    {
167
        return $this->decorated->createFromPayload($user, $payload);
0 ignored issues
show
Bug introduced by
The method createFromPayload() does not exist on Lexik\Bundle\JWTAuthenti...WTTokenManagerInterface. Did you maybe mean create()? ( Ignorable by Annotation )

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

167
        return $this->decorated->/** @scrutinizer ignore-call */ createFromPayload($user, $payload);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
168
    }
169
}
170