vendor/lcobucci/jwt/src/Token/Parser.php line 54

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Lcobucci\JWT\Token;
  4. use DateTimeImmutable;
  5. use Lcobucci\JWT\Decoder;
  6. use Lcobucci\JWT\Parser as ParserInterface;
  7. use Lcobucci\JWT\Token as TokenInterface;
  8. use function array_key_exists;
  9. use function count;
  10. use function explode;
  11. use function is_array;
  12. use function is_numeric;
  13. use function number_format;
  14. final class Parser implements ParserInterface
  15. {
  16. private const MICROSECOND_PRECISION = 6;
  17. private Decoder $decoder;
  18. public function __construct(Decoder $decoder)
  19. {
  20. $this->decoder = $decoder;
  21. }
  22. public function parse(string $jwt): TokenInterface
  23. {
  24. [$encodedHeaders, $encodedClaims, $encodedSignature] = $this->splitJwt($jwt);
  25. $header = $this->parseHeader($encodedHeaders);
  26. return new Plain(
  27. new DataSet($header, $encodedHeaders),
  28. new DataSet($this->parseClaims($encodedClaims), $encodedClaims),
  29. $this->parseSignature($header, $encodedSignature)
  30. );
  31. }
  32. /**
  33. * Splits the JWT string into an array
  34. *
  35. * @return string[]
  36. *
  37. * @throws InvalidTokenStructure When JWT doesn't have all parts.
  38. */
  39. private function splitJwt(string $jwt): array
  40. {
  41. $data = explode('.', $jwt);
  42. if (count($data) !== 3) {
  43. throw InvalidTokenStructure::missingOrNotEnoughSeparators();
  44. }
  45. return $data;
  46. }
  47. /**
  48. * Parses the header from a string
  49. *
  50. * @return mixed[]
  51. *
  52. * @throws UnsupportedHeaderFound When an invalid header is informed.
  53. * @throws InvalidTokenStructure When parsed content isn't an array.
  54. */
  55. private function parseHeader(string $data): array
  56. {
  57. $header = $this->decoder->jsonDecode($this->decoder->base64UrlDecode($data));
  58. if (! is_array($header)) {
  59. throw InvalidTokenStructure::arrayExpected('headers');
  60. }
  61. if (array_key_exists('enc', $header)) {
  62. throw UnsupportedHeaderFound::encryption();
  63. }
  64. if (! array_key_exists('typ', $header)) {
  65. $header['typ'] = 'JWT';
  66. }
  67. return $header;
  68. }
  69. /**
  70. * Parses the claim set from a string
  71. *
  72. * @return mixed[]
  73. *
  74. * @throws InvalidTokenStructure When parsed content isn't an array or contains non-parseable dates.
  75. */
  76. private function parseClaims(string $data): array
  77. {
  78. $claims = $this->decoder->jsonDecode($this->decoder->base64UrlDecode($data));
  79. if (! is_array($claims)) {
  80. throw InvalidTokenStructure::arrayExpected('claims');
  81. }
  82. if (array_key_exists(RegisteredClaims::AUDIENCE, $claims)) {
  83. $claims[RegisteredClaims::AUDIENCE] = (array) $claims[RegisteredClaims::AUDIENCE];
  84. }
  85. foreach (RegisteredClaims::DATE_CLAIMS as $claim) {
  86. if (! array_key_exists($claim, $claims)) {
  87. continue;
  88. }
  89. $claims[$claim] = $this->convertDate($claims[$claim]);
  90. }
  91. return $claims;
  92. }
  93. /**
  94. * @param int|float|string $timestamp
  95. *
  96. * @throws InvalidTokenStructure
  97. */
  98. private function convertDate($timestamp): DateTimeImmutable
  99. {
  100. if (! is_numeric($timestamp)) {
  101. throw InvalidTokenStructure::dateIsNotParseable($timestamp);
  102. }
  103. $normalizedTimestamp = number_format((float) $timestamp, self::MICROSECOND_PRECISION, '.', '');
  104. $date = DateTimeImmutable::createFromFormat('U.u', $normalizedTimestamp);
  105. if ($date === false) {
  106. throw InvalidTokenStructure::dateIsNotParseable($normalizedTimestamp);
  107. }
  108. return $date;
  109. }
  110. /**
  111. * Returns the signature from given data
  112. *
  113. * @param mixed[] $header
  114. */
  115. private function parseSignature(array $header, string $data): Signature
  116. {
  117. if ($data === '' || ! array_key_exists('alg', $header) || $header['alg'] === 'none') {
  118. return Signature::fromEmptyData();
  119. }
  120. $hash = $this->decoder->base64UrlDecode($data);
  121. return new Signature($hash, $data);
  122. }
  123. }