This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Zenstruck\JWT; |
||
4 | |||
5 | use Zenstruck\JWT\Exception\MalformedToken; |
||
6 | use Zenstruck\JWT\Exception\UnverifiedToken; |
||
7 | use Zenstruck\JWT\Exception\ValidationFailed; |
||
8 | |||
9 | /** |
||
10 | * @author Kevin Bond <[email protected]> |
||
11 | */ |
||
12 | final class Token |
||
13 | { |
||
14 | const HEADER_TYP = 'typ'; |
||
15 | const HEADER_ALG = 'alg'; |
||
16 | |||
17 | const CLAIM_ISS = 'iss'; |
||
18 | const CLAIM_SUB = 'sub'; |
||
19 | const CLAIM_AUD = 'aud'; |
||
20 | const CLAIM_EXP = 'exp'; |
||
21 | const CLAIM_NBF = 'nbf'; |
||
22 | const CLAIM_IAT = 'iat'; |
||
23 | const CLAIM_JTI = 'jti'; |
||
24 | |||
25 | private $headers; |
||
26 | private $claims; |
||
27 | private $signature; |
||
28 | |||
29 | /** |
||
30 | * @param string $token |
||
31 | * |
||
32 | * @return self |
||
33 | * |
||
34 | * @throws MalformedToken |
||
35 | */ |
||
36 | 25 | public static function fromString($token) |
|
37 | { |
||
38 | 25 | if (!is_string($token)) { |
|
39 | 2 | throw new MalformedToken($token); |
|
40 | } |
||
41 | |||
42 | 25 | $parts = explode('.', $token); |
|
43 | |||
44 | 25 | if (3 !== count($parts)) { |
|
45 | 1 | throw new MalformedToken($token); |
|
46 | } |
||
47 | |||
48 | try { |
||
49 | 25 | $headers = self::jsonDecode($parts[0]); |
|
50 | 25 | $claims = self::jsonDecode($parts[1]); |
|
51 | 25 | } catch (\InvalidArgumentException $e) { |
|
52 | 1 | throw new MalformedToken($token, $e); |
|
53 | } |
||
54 | |||
55 | 25 | if (!is_array($headers) || !is_array($claims)) { |
|
56 | 1 | throw new MalformedToken($token); |
|
57 | } |
||
58 | |||
59 | 25 | return new self($claims, $headers, $parts[2]); |
|
60 | } |
||
61 | |||
62 | /** |
||
63 | * @param array $claims |
||
64 | * @param array $headers |
||
65 | * @param string|null $signature |
||
66 | */ |
||
67 | 55 | public function __construct(array $claims, array $headers = [], $signature = null) |
|
68 | { |
||
69 | 55 | $this->headers = $headers; |
|
70 | 55 | $this->claims = $claims; |
|
71 | 55 | $this->signature = $signature; |
|
72 | 55 | } |
|
73 | |||
74 | /** |
||
75 | * {@inheritdoc} |
||
76 | */ |
||
77 | 11 | public function __toString() |
|
78 | { |
||
79 | 11 | return sprintf('%s.%s', $this->createPayload(), $this->signature); |
|
80 | } |
||
81 | |||
82 | /** |
||
83 | * @param Signer $signer |
||
84 | * @param mixed $key |
||
85 | * |
||
86 | * @return self |
||
87 | */ |
||
88 | 29 | public function sign(Signer $signer, $key) |
|
89 | { |
||
90 | 29 | $this->headers[self::HEADER_ALG] = $signer->name(); |
|
91 | 29 | $this->signature = self::encode($signer->sign($this->createPayload(), $key)); |
|
92 | |||
93 | 29 | return $this; |
|
94 | } |
||
95 | |||
96 | /** |
||
97 | * @param Signer $signer |
||
98 | * @param mixed $key |
||
99 | * |
||
100 | * @return self |
||
101 | * |
||
102 | * @throws UnverifiedToken |
||
103 | */ |
||
104 | 29 | public function verify(Signer $signer, $key) |
|
105 | { |
||
106 | 29 | if ($this->algorithm() !== $signer->name()) { |
|
107 | 10 | throw new UnverifiedToken($this); |
|
108 | } |
||
109 | |||
110 | 19 | if (!$signer->verify($this->createPayload(), $this->decode($this->signature), $key)) { |
|
111 | 9 | throw new UnverifiedToken($this); |
|
112 | } |
||
113 | |||
114 | 10 | return $this; |
|
115 | } |
||
116 | |||
117 | /** |
||
118 | * @param Validator $validator |
||
119 | * |
||
120 | * @return self |
||
121 | * |
||
122 | * @throws ValidationFailed |
||
123 | */ |
||
124 | 26 | public function validate(Validator $validator) |
|
125 | { |
||
126 | 26 | $validator->validate($this); |
|
127 | |||
128 | 9 | return $this; |
|
129 | } |
||
130 | |||
131 | /** |
||
132 | * @return array |
||
133 | */ |
||
134 | 32 | public function headers() |
|
135 | { |
||
136 | 32 | return $this->headers; |
|
137 | } |
||
138 | |||
139 | /** |
||
140 | * @return array |
||
141 | */ |
||
142 | 32 | public function claims() |
|
143 | { |
||
144 | 32 | return $this->claims; |
|
145 | } |
||
146 | |||
147 | /** |
||
148 | * @return string |
||
149 | */ |
||
150 | 30 | public function algorithm() |
|
151 | { |
||
152 | 30 | return strtoupper($this->getHeader(self::HEADER_ALG, 'NONE')); |
|
153 | } |
||
154 | |||
155 | /** |
||
156 | * @return string|null |
||
157 | */ |
||
158 | 1 | public function issuer() |
|
159 | { |
||
160 | 1 | return $this->get(self::CLAIM_ISS); |
|
161 | } |
||
162 | |||
163 | /** |
||
164 | * @return string|null |
||
165 | */ |
||
166 | 1 | public function subject() |
|
167 | { |
||
168 | 1 | return $this->get(self::CLAIM_SUB); |
|
169 | } |
||
170 | |||
171 | /** |
||
172 | * @return string|null |
||
173 | */ |
||
174 | 1 | public function audience() |
|
175 | { |
||
176 | 1 | return $this->get(self::CLAIM_AUD); |
|
177 | } |
||
178 | |||
179 | /** |
||
180 | * @return string|null |
||
181 | */ |
||
182 | 1 | public function id() |
|
183 | { |
||
184 | 1 | return $this->get(self::CLAIM_JTI); |
|
185 | } |
||
186 | |||
187 | /** |
||
188 | * @return \DateTime|null |
||
189 | */ |
||
190 | 6 | public function expiresAt() |
|
191 | { |
||
192 | 6 | return $this->getDateClaim(self::CLAIM_EXP); |
|
193 | } |
||
194 | |||
195 | /** |
||
196 | * @return \DateTime|null |
||
197 | */ |
||
198 | 2 | public function issuedAt() |
|
199 | { |
||
200 | 2 | return $this->getDateClaim(self::CLAIM_IAT); |
|
201 | } |
||
202 | |||
203 | /** |
||
204 | * @return \DateTime|null |
||
205 | */ |
||
206 | 4 | public function notBefore() |
|
207 | { |
||
208 | 4 | return $this->getDateClaim(self::CLAIM_NBF); |
|
209 | } |
||
210 | |||
211 | /** |
||
212 | * @param \DateTime|null $currentTime |
||
213 | * |
||
214 | * @return bool |
||
215 | */ |
||
216 | 5 | View Code Duplication | public function isExpired(\DateTime $currentTime = null) |
0 ignored issues
–
show
|
|||
217 | { |
||
218 | 5 | if (null === $expiresAt = $this->get(self::CLAIM_EXP)) { |
|
219 | 1 | return false; |
|
220 | } |
||
221 | |||
222 | 5 | $currentTime = $currentTime ?: new \DateTime('now'); |
|
223 | |||
224 | 5 | return $expiresAt < $currentTime->getTimestamp(); |
|
225 | } |
||
226 | |||
227 | /** |
||
228 | * @param \DateTime|null $currentTime |
||
229 | * |
||
230 | * @return bool |
||
231 | */ |
||
232 | 3 | View Code Duplication | public function isAcceptable(\DateTime $currentTime = null) |
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
233 | { |
||
234 | 3 | if (null === $notBefore = $this->get(self::CLAIM_NBF)) { |
|
235 | 1 | return true; |
|
236 | } |
||
237 | |||
238 | 3 | $currentTime = $currentTime ?: new \DateTime('now'); |
|
239 | |||
240 | 3 | return $currentTime->getTimestamp() > $notBefore; |
|
241 | } |
||
242 | |||
243 | /** |
||
244 | * @param string $key |
||
245 | * @param mixed|null $default |
||
246 | * |
||
247 | * @return mixed |
||
248 | */ |
||
249 | 32 | public function get($key, $default = null) |
|
250 | { |
||
251 | 32 | if (isset($this->claims[$key])) { |
|
252 | 22 | return $this->claims[$key]; |
|
253 | } |
||
254 | |||
255 | 14 | return $default; |
|
256 | } |
||
257 | |||
258 | /** |
||
259 | * @param string $key |
||
260 | * @param mixed|null $default |
||
261 | * |
||
262 | * @return mixed |
||
263 | */ |
||
264 | 31 | public function getHeader($key, $default = null) |
|
265 | { |
||
266 | 31 | if (isset($this->headers[$key])) { |
|
267 | 30 | return $this->headers[$key]; |
|
268 | } |
||
269 | |||
270 | 1 | return $default; |
|
271 | } |
||
272 | |||
273 | /** |
||
274 | * @param string $data |
||
275 | * |
||
276 | * @return string |
||
277 | */ |
||
278 | 30 | private static function encode($data) |
|
279 | { |
||
280 | 30 | return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); |
|
281 | } |
||
282 | |||
283 | /** |
||
284 | * @param string $data |
||
285 | * |
||
286 | * @return string |
||
287 | */ |
||
288 | 34 | private static function decode($data) |
|
289 | { |
||
290 | 34 | return base64_decode(strtr($data, '-_', '+/')); |
|
291 | } |
||
292 | |||
293 | /** |
||
294 | * @param string $data |
||
295 | * |
||
296 | * @return array |
||
297 | * |
||
298 | * @throws \InvalidArgumentException |
||
299 | */ |
||
300 | 25 | private static function jsonDecode($data) |
|
301 | { |
||
302 | 25 | $decoded = json_decode(self::decode($data), true); |
|
303 | |||
304 | 25 | if (JSON_ERROR_NONE !== json_last_error()) { |
|
305 | 1 | throw new \InvalidArgumentException(sprintf('Error decoding JSON string "%s".', $data)); |
|
306 | } |
||
307 | |||
308 | 25 | return $decoded; |
|
309 | } |
||
310 | |||
311 | /** |
||
312 | * @return string |
||
313 | */ |
||
314 | 30 | private function createPayload() |
|
315 | { |
||
316 | 30 | return sprintf('%s.%s', |
|
317 | 30 | self::encode(json_encode($this->headers(), JSON_UNESCAPED_SLASHES)), |
|
318 | 30 | self::encode(json_encode($this->claims(), JSON_UNESCAPED_SLASHES)) |
|
319 | 30 | ); |
|
320 | } |
||
321 | |||
322 | /** |
||
323 | * @param string $claim |
||
324 | * |
||
325 | * @return \DateTime|null |
||
326 | */ |
||
327 | 12 | private function getDateClaim($claim) |
|
328 | { |
||
329 | 12 | if (null === $value = $this->get($claim)) { |
|
330 | 5 | return null; |
|
331 | } |
||
332 | |||
333 | 10 | return \DateTime::createFromFormat('U', $value); |
|
334 | } |
||
335 | } |
||
336 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.