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\UserNotFoundException as SymfonyUserNotFoundException; |
||||
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); |
||||
111 | } catch (SymfonyUserNotFoundException $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 been 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); |
||||
0 ignored issues
–
show
|
|||||
144 | } |
||||
145 | |||||
146 | /** |
||||
147 | * {@inheritdoc} |
||||
148 | */ |
||||
149 | public function getUserIdentityField(): string |
||||
150 | { |
||||
151 | return $this->decorated->getUserIdentityField(); |
||||
0 ignored issues
–
show
The function
Lexik\Bundle\JWTAuthenti...:getUserIdentityField() has been deprecated: since 2.15, use {@see UserInterface::getUserIdentifier()} instead
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
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
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
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. ![]() |
|||||
168 | } |
||||
169 | } |
||||
170 |
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.