Passed
Push — master ( 9f9983...fa69ac )
by Daniel
06:13
created

JWTManager::create()   A

Complexity

Conditions 3
Paths 6

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 3
eloc 8
c 4
b 0
f 0
nc 6
nop 2
dl 0
loc 16
ccs 0
cts 7
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\Entity\Core\AbstractRefreshToken;
23
use Silverback\ApiComponentsBundle\Event\JWTRefreshedEvent;
24
use Silverback\ApiComponentsBundle\RefreshToken\RefreshToken;
25
use Silverback\ApiComponentsBundle\RefreshToken\Storage\RefreshTokenStorageInterface;
26
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
27
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
28
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
29
use Symfony\Component\Security\Core\User\UserInterface;
30
use Symfony\Component\Security\Core\User\UserProviderInterface;
31
32
/**
33
 * @author Vincent Chalamon <[email protected]>
34
 */
35
final class JWTManager implements JWTTokenManagerInterface
36
{
37
    private JWTTokenManagerInterface $decorated;
38
    private EventDispatcherInterface $dispatcher;
39
    private UserProviderInterface $userProvider;
40
    private RefreshTokenStorageInterface $storage;
41
42
    public function __construct(JWTTokenManagerInterface $decorated, EventDispatcherInterface $dispatcher, UserProviderInterface $userProvider, RefreshTokenStorageInterface $storage)
43
    {
44
        $this->decorated = $decorated;
45
        $this->dispatcher = $dispatcher;
46
        $this->userProvider = $userProvider;
47
        $this->storage = $storage;
48
    }
49
50
    /**
51
     * {@inheritdoc}
52
     */
53
    public function create(UserInterface $user, ?AbstractRefreshToken $token = null): string
54
    {
55
        try {
56
            if ($token) {
57
                $this->storage->expireToken($token);
58
            } else {
59
                $this->storage->expireAll($user);
60
            }
61
            $this->storage->create($user);
62
        } catch (OptimisticLockException $exception) {
63
            // do nothing, we have already modified the refresh token.
64
            // we can continue to generate a jwt token, won't make any difference
65
            // if the user has a new one of these...
66
        }
67
68
        return $this->decorated->create($user);
69
    }
70
71
    /**
72
     * {@inheritdoc}
73
     */
74
    public function decode(TokenInterface $token)
75
    {
76
        try {
77
            return $this->decorated->decode($token);
78
        } catch (JWTDecodeFailureException $exception) {
79
            if (JWTDecodeFailureException::EXPIRED_TOKEN !== $exception->getReason()) {
80
                throw $exception;
81
            }
82
83
            $payload = $exception->getPayload();
84
            $idClaim = $this->getUserIdClaim();
85
86
            if (!isset($payload[$idClaim])) {
87
                throw new InvalidPayloadException($idClaim);
88
            }
89
90
            $identity = $payload[$idClaim];
91
            try {
92
                $user = $this->userProvider->loadUserByUsername($identity);
93
            } catch (UsernameNotFoundException $e) {
94
                throw new UserNotFoundException($idClaim, $identity);
95
            }
96
97
            $refreshToken = $this->resolveCurrentRefreshToken($user);
98
            if (!$refreshToken) {
99
                throw $exception;
100
            }
101
102
            $accessToken = $this->create($user, $refreshToken);
103
104
            $this->dispatcher->dispatch(new JWTRefreshedEvent($accessToken));
105
106
            return $this->decorated->decode(new PreAuthenticationJWTUserToken($accessToken));
107
        }
108
    }
109
110
    private function resolveCurrentRefreshToken(UserInterface $user, int $retries = 0): ?RefreshToken
111
    {
112
        // the refresh token could have just ben expired and is being refreshed by another request
113
        $refreshToken = $this->storage->findOneByUser($user);
114
        if (!$refreshToken || $refreshToken->isExpired()) {
115
            if ($retries < 1) {
116
                ++$retries;
117
                sleep(1);
118
119
                return $this->resolveCurrentRefreshToken($user, $retries);
120
            }
121
122
            return null;
123
        }
124
125
        return $refreshToken;
126
    }
127
128
    /**
129
     * {@inheritdoc}
130
     */
131
    public function setUserIdentityField($field)
132
    {
133
        return $this->decorated->setUserIdentityField($field);
134
    }
135
136
    /**
137
     * {@inheritdoc}
138
     */
139
    public function getUserIdentityField(): string
140
    {
141
        return $this->decorated->getUserIdentityField();
142
    }
143
144
    /**
145
     * {@inheritdoc}
146
     */
147
    public function getUserIdClaim(): string
148
    {
149
        return $this->decorated->getUserIdClaim();
150
    }
151
152
    /**
153
     * {@inheritdoc}
154
     */
155
    public function createFromPayload(UserInterface $user, array $payload)
156
    {
157
        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

157
        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...
158
    }
159
}
160