GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 70b15e...4c8633 )
by Joni
04:39
created

JWT::_validatedPayloadFromJWS()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 9.4285
cc 2
eloc 5
nc 2
nop 2
crap 2
1
<?php
2
3
namespace JWX\JWT;
4
5
use JWX\JWE\CompressionAlgorithm;
6
use JWX\JWE\ContentEncryptionAlgorithm;
7
use JWX\JWE\JWE;
8
use JWX\JWE\KeyManagementAlgorithm;
9
use JWX\JWK\JWKSet;
10
use JWX\JWS\Algorithm\NoneAlgorithm;
11
use JWX\JWS\JWS;
12
use JWX\JWS\SignatureAlgorithm;
13
use JWX\JWT\Claims;
14
use JWX\JWT\Exception\ValidationException;
15
use JWX\JWT\Header\Header;
16
use JWX\JWT\Header\JOSE;
17
use JWX\JWT\Parameter\ContentTypeParameter;
18
use JWX\Util\Base64;
19
20
21
/**
22
 * Represents a token as a JWS or a JWE compact serialization with claims
23
 * as a payload.
24
 *
25
 * @link https://tools.ietf.org/html/rfc7519#section-3
26
 */
27
class JWT
28
{
29
	/**
30
	 * Type identifier for signed JWT.
31
	 *
32
	 * @internal
33
	 *
34
	 * @var int
35
	 */
36
	const TYPE_JWS = 0;
37
	
38
	/**
39
	 * Type identifier for encrypted JWT.
40
	 *
41
	 * @internal
42
	 *
43
	 * @var int
44
	 */
45
	const TYPE_JWE = 1;
46
	
47
	/**
48
	 * JWT parts.
49
	 *
50
	 * @var string[] $_parts
51
	 */
52
	protected $_parts;
53
	
54
	/**
55
	 * JWT type.
56
	 *
57
	 * @var int $_type
58
	 */
59
	protected $_type;
60
	
61
	/**
62
	 * Constructor
63
	 *
64
	 * @param string $token JWT string
65
	 * @throws \UnexpectedValueException
66
	 */
67 14
	public function __construct($token) {
68 14
		$this->_parts = explode(".", $token);
69 14
		switch (count($this->_parts)) {
70 14
		case 3:
71 9
			$this->_type = self::TYPE_JWS;
72 9
			break;
73 5
		case 5:
74 4
			$this->_type = self::TYPE_JWE;
75 4
			break;
76 1
		default:
77 1
			throw new \UnexpectedValueException("Not a JWT token.");
78 14
		}
79 13
	}
80
	
81
	/**
82
	 * Convert claims set to an unsecured JWT.
83
	 *
84
	 * @param Claims $claims Claims set
85
	 * @param Header|null $header Optional header
86
	 * @throws \RuntimeException For generic errors
87
	 * @return self
88
	 */
89 3
	public static function unsecuredFromClaims(Claims $claims, 
90
			Header $header = null) {
91 3
		return self::signedFromClaims($claims, new NoneAlgorithm(), $header);
92
	}
93
	
94
	/**
95
	 * Convert claims set to a signed JWS token.
96
	 *
97
	 * @param Claims $claims Claims set
98
	 * @param SignatureAlgorithm $algo Signature algorithm
99
	 * @param Header|null $header Optional header
100
	 * @throws \RuntimeException For generic errors
101
	 * @return self
102
	 */
103 5
	public static function signedFromClaims(Claims $claims, 
104
			SignatureAlgorithm $algo, Header $header = null) {
105 5
		$payload = $claims->toJSON();
106 5
		$jws = JWS::sign($payload, $algo, $header);
107 5
		return new self($jws->toCompact());
108
	}
109
	
110
	/**
111
	 * Convert claims set to an encrypted JWE token.
112
	 *
113
	 * @param Claims $claims Claims set
114
	 * @param KeyManagementAlgorithm $key_algo Key management algorithm
115
	 * @param ContentEncryptionAlgorithm $enc_algo Content encryption algorithm
116
	 * @param CompressionAlgorithm|null $zip_algo Optional compression algorithm
117
	 * @param Header|null $header Optional header
118
	 * @throws \RuntimeException For generic errors
119
	 * @return self
120
	 */
121 2
	public static function encryptedFromClaims(Claims $claims, 
122
			KeyManagementAlgorithm $key_algo, 
123
			ContentEncryptionAlgorithm $enc_algo, 
124
			CompressionAlgorithm $zip_algo = null, Header $header = null) {
125 2
		$payload = $claims->toJSON();
126 2
		$jwe = JWE::encrypt($payload, $key_algo, $enc_algo, $zip_algo, $header);
127 2
		return new self($jwe->toCompact());
128
	}
129
	
130
	/**
131
	 * Sign self producing a nested JWT.
132
	 *
133
	 * Note that if JWT is to be signed and encrypted, it should be done in
134
	 * sign-then-encrypt order. Please refer to links for security information.
135
	 *
136
	 * @link https://tools.ietf.org/html/rfc7519#section-11.2
137
	 * @param SignatureAlgorithm $algo Signature algorithm
138
	 * @param Header|null $header Optional header
139
	 * @throws \RuntimeException For generic errors
140
	 * @return self
141
	 */
142 1
	public function signNested(SignatureAlgorithm $algo, Header $header = null) {
143 1
		if (!isset($header)) {
144 1
			$header = new Header();
145 1
		}
146
		// add JWT content type parameter
147 1
		$header = $header->withParameters(
148 1
			new ContentTypeParameter(ContentTypeParameter::TYPE_JWT));
149 1
		$jws = JWS::sign($this->token(), $algo, $header);
150 1
		return new self($jws->toCompact());
151
	}
152
	
153
	/**
154
	 * Encrypt self producing a nested JWT.
155
	 *
156
	 * This JWT should be a JWS, that is, the order of nesting should be
157
	 * sign-then-encrypt.
158
	 *
159
	 * @link https://tools.ietf.org/html/rfc7519#section-11.2
160
	 * @param KeyManagementAlgorithm $key_algo Key management algorithm
161
	 * @param ContentEncryptionAlgorithm $enc_algo Content encryption algorithm
162
	 * @param CompressionAlgorithm|null $zip_algo Optional compression algorithm
163
	 * @param Header|null $header Optional header
164
	 * @throws \RuntimeException For generic errors
165
	 * @return self
166
	 */
167 1
	public function encryptNested(KeyManagementAlgorithm $key_algo, 
168
			ContentEncryptionAlgorithm $enc_algo, 
169
			CompressionAlgorithm $zip_algo = null, Header $header = null) {
170 1
		if (!isset($header)) {
171 1
			$header = new Header();
172 1
		}
173
		// add JWT content type parameter
174 1
		$header = $header->withParameters(
175 1
			new ContentTypeParameter(ContentTypeParameter::TYPE_JWT));
176 1
		$jwe = JWE::encrypt($this->token(), $key_algo, $enc_algo, $zip_algo, 
177 1
			$header);
178 1
		return new self($jwe->toCompact());
179
	}
180
	
181
	/**
182
	 * Whether JWT is a JWS.
183
	 *
184
	 * @return bool
185
	 */
186 16
	public function isJWS() {
187 16
		return $this->_type == self::TYPE_JWS;
188
	}
189
	
190
	/**
191
	 * Get JWT as a JWS.
192
	 *
193
	 * @throws \LogicException
194
	 * @return JWS
195
	 */
196 12
	public function JWS() {
197 12
		if (!$this->isJWS()) {
198 1
			throw new \LogicException("Not a JWS.");
199
		}
200 11
		return JWS::fromParts($this->_parts);
201
	}
202
	
203
	/**
204
	 * Whether JWT is a JWE.
205
	 *
206
	 * @return bool
207
	 */
208 11
	public function isJWE() {
209 11
		return $this->_type == self::TYPE_JWE;
210
	}
211
	
212
	/**
213
	 * Get JWT as a JWE.
214
	 *
215
	 * @throws \LogicException
216
	 * @return JWE
217
	 */
218 7
	public function JWE() {
219 7
		if (!$this->isJWE()) {
220 1
			throw new \LogicException("Not a JWE.");
221
		}
222 6
		return JWE::fromParts($this->_parts);
223
	}
224
	
225
	/**
226
	 * Check whether JWT contains another nested JWT.
227
	 *
228
	 * @return bool
229
	 */
230 9
	public function isNested() {
231 9
		$header = $this->header();
232 9
		if (!$header->hasContentType()) {
233 7
			return false;
234
		}
235 3
		$cty = $header->contentType()->value();
236 3
		if ($cty != ContentTypeParameter::TYPE_JWT) {
237 1
			return false;
238
		}
239 2
		return true;
240
	}
241
	
242
	/**
243
	 * Check whether JWT is unsecured, that is, it's neither integrity protected
244
	 * nor encrypted.
245
	 *
246
	 * @return bool
247
	 */
248 4
	public function isUnsecured() {
249
		// encrypted JWT shall be considered secure
250 4
		if ($this->isJWE()) {
251 2
			return false;
252
		}
253
		// check whether JWS is unsecured
254 2
		return $this->JWS()->isUnsecured();
255
	}
256
	
257
	/**
258
	 * Get JWT header.
259
	 *
260
	 * @return JOSE
261
	 */
262 11
	public function header() {
263 11
		$header = Header::fromJSON(Base64::urlDecode($this->_parts[0]));
264 11
		return new JOSE($header);
265
	}
266
	
267
	/**
268
	 * Get JWT as a string.
269
	 *
270
	 * @return string
271
	 */
272 6
	public function token() {
273 6
		return implode(".", $this->_parts);
274
	}
275
	
276
	/**
277
	 * Get claims from the JWT.
278
	 *
279
	 * Claims shall be validated according to given validation context.
280
	 * Validation context must contain all the necessary keys for the signature
281
	 * validation and/or content decryption.
282
	 *
283
	 * @param ValidationContext $ctx
284
	 * @throws ValidationException If signature is invalid, or decryption fails,
285
	 *         or claims validation fails.
286
	 * @throws \RuntimeException For generic errors
287
	 * @return Claims
288
	 */
289 11
	public function claims(ValidationContext $ctx) {
290
		// check signature or decrypt depending on the JWT type.
291 11
		if ($this->isJWS()) {
292 8
			$payload = self::_validatedPayloadFromJWS($this->JWS(), $ctx);
293 4
		} else {
294 4
			$payload = self::_validatedPayloadFromJWE($this->JWE(), $ctx);
295
		}
296
		// if JWT contains a nested token
297 6
		if ($this->isNested()) {
298 1
			return $this->_claimsFromNestedPayload($payload, $ctx);
299
		}
300
		// decode claims and validate
301 6
		$claims = Claims::fromJSON($payload);
302 6
		$ctx->validate($claims);
303 6
		return $claims;
304
	}
305
	
306
	/**
307
	 * Get claims from a nested payload.
308
	 *
309
	 * @param string $payload JWT payload
310
	 * @param ValidationContext $ctx Validation context
311
	 * @return Claims
312
	 */
313 1
	private function _claimsFromNestedPayload($payload, ValidationContext $ctx) {
314 1
		$jwt = new JWT($payload);
315
		// if this token secured, allow nested tokens to be unsecured.
316 1
		if (!$this->isUnsecured()) {
317 1
			$ctx = $ctx->withUnsecuredAllowed(true);
318 1
		}
319 1
		return $jwt->claims($ctx);
320
	}
321
	
322
	/**
323
	 * Get payload from JWS.
324
	 *
325
	 * @param JWS $jws JWS
326
	 * @param ValidationContext $ctx Validation context
327
	 * @throws ValidationException If signature validation fails
328
	 * @return string
329
	 */
330 8
	private static function _validatedPayloadFromJWS(JWS $jws, 
331
			ValidationContext $ctx) {
332 8
		if ($jws->isUnsecured()) {
333 3
			return self::_validatedPayloadFromUnsecuredJWS($jws, $ctx);
334
		}
335 5
		return self::_validatedPayloadFromSignedJWS($jws, $ctx->keys());
336
	
337
	}
338
	
339
	/**
340
	 * Get validated payload from an unsecured JWS.
341
	 *
342
	 * @param JWS $jws
343
	 * @param ValidationContext $ctx
344
	 * @throws ValidationException
345
	 * @return string
346
	 */
347 3
	private static function _validatedPayloadFromUnsecuredJWS(JWS $jws, 
348
			ValidationContext $ctx) {
349 3
		if (!$ctx->isUnsecuredAllowed()) {
350 1
			throw new ValidationException("Unsecured JWS not allowed.");
351
		}
352 2
		if (!$jws->validate(new NoneAlgorithm())) {
353 1
			throw new ValidationException("Malformed unsecured token.");
354
		}
355 1
		return $jws->payload();
356
	}
357
	
358
	/**
359
	 * Get validated payload from a signed JWS.
360
	 *
361
	 * @param JWS $jws
362
	 * @param JWKSet $keys
363
	 * @throws ValidationException
364
	 * @return string
365
	 */
366 5
	private static function _validatedPayloadFromSignedJWS(JWS $jws, 
367
			JWKSet $keys) {
368
		try {
369 5
			if (1 == count($keys)) {
370 2
				$valid = $jws->validateWithJWK($keys->first());
371 2
			} else {
372 3
				$valid = $jws->validateWithJWKSet($keys);
373
			}
374 5
		} catch (\RuntimeException $e) {
375 1
			throw new ValidationException("JWS validation failed.", null, $e);
376
		}
377 4
		if (!$valid) {
378 1
			throw new ValidationException("JWS signature is invalid.");
379
		}
380 3
		return $jws->payload();
381
	}
382
	
383
	/**
384
	 * Get validated payload from an encrypted JWE.
385
	 *
386
	 * @param JWE $jwe JWE
387
	 * @param ValidationContext $ctx Validation context
388
	 * @throws ValidationException If decryption fails
389
	 * @return string
390
	 */
391 4
	private static function _validatedPayloadFromJWE(JWE $jwe, 
392
			ValidationContext $ctx) {
393
		try {
394 4
			$keys = $ctx->keys();
395 4
			if (1 == count($keys)) {
396 1
				return $jwe->decryptWithJWK($keys->first());
397
			}
398 3
			return $jwe->decryptWithJWKSet($keys);
399 1
		} catch (\RuntimeException $e) {
400 1
			throw new ValidationException("JWE validation failed.", null, $e);
401
		}
402
	}
403
	
404
	/**
405
	 * Convert JWT to string.
406
	 *
407
	 * @return string
408
	 */
409 1
	public function __toString() {
410 1
		return $this->token();
411
	}
412
}
413