127 lines
5.2 KiB
PHP
127 lines
5.2 KiB
PHP
<?php
|
|
|
|
namespace Lexik\Bundle\JWTAuthenticationBundle\Services\WebToken;
|
|
|
|
use Jose\Bundle\JoseFramework\Services\ClaimCheckerManager;
|
|
use Jose\Bundle\JoseFramework\Services\ClaimCheckerManagerFactory;
|
|
use Jose\Bundle\JoseFramework\Services\HeaderCheckerManager;
|
|
use Jose\Bundle\JoseFramework\Services\JWELoader;
|
|
use Jose\Bundle\JoseFramework\Services\JWELoaderFactory;
|
|
use Jose\Bundle\JoseFramework\Services\JWSLoader;
|
|
use Jose\Bundle\JoseFramework\Services\JWSLoaderFactory;
|
|
use Jose\Component\Checker\InvalidClaimException;
|
|
use Jose\Component\Checker\MissingMandatoryClaimException;
|
|
use Jose\Component\Core\JWKSet;
|
|
use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTDecodeFailureException;
|
|
|
|
final class AccessTokenLoader
|
|
{
|
|
private $jwsLoader;
|
|
private $jwsHeaderCheckerManager;
|
|
private $claimCheckerManager;
|
|
private $jweLoader;
|
|
private $signatureKeyset;
|
|
private $encryptionKeyset;
|
|
|
|
/**
|
|
* @var string[]
|
|
*/
|
|
private $mandatoryClaims;
|
|
|
|
private $continueOnDecryptionFailure;
|
|
|
|
public function __construct(
|
|
JWSLoaderFactory $jwsLoaderFactory,
|
|
?JWELoaderFactory $jweLoaderFactory,
|
|
ClaimCheckerManagerFactory $claimCheckerManagerFactory,
|
|
array $claimChecker,
|
|
array $jwsHeaderChecker,
|
|
array $mandatoryClaims,
|
|
array $signatureAlgorithms,
|
|
string $signatureKeyset,
|
|
?bool $continueOnDecryptionFailure,
|
|
?array $jweHeaderChecker,
|
|
?array $keyEncryptionAlgorithms,
|
|
?array $contentEncryptionAlgorithms,
|
|
?string $encryptionKeyset
|
|
) {
|
|
$this->jwsLoader = $jwsLoaderFactory->create(['jws_compact'], $signatureAlgorithms, $jwsHeaderChecker);
|
|
if ($jweLoaderFactory !== null && $keyEncryptionAlgorithms !== null && $contentEncryptionAlgorithms !== null && $jweHeaderChecker !== null) {
|
|
$this->jweLoader = $jweLoaderFactory->create(['jwe_compact'], array_merge($keyEncryptionAlgorithms, $contentEncryptionAlgorithms), null, null, $jweHeaderChecker);
|
|
$this->continueOnDecryptionFailure = $continueOnDecryptionFailure;
|
|
}
|
|
$this->signatureKeyset = JWKSet::createFromJson($signatureKeyset);
|
|
$this->encryptionKeyset = $encryptionKeyset ? JWKSet::createFromJson($encryptionKeyset) : null;
|
|
$this->claimCheckerManager = $claimCheckerManagerFactory->create($claimChecker);
|
|
$this->mandatoryClaims = $mandatoryClaims;
|
|
}
|
|
|
|
public function load(string $token): array
|
|
{
|
|
$token = $this->loadJWE($token);
|
|
$data = $this->loadJWS($token);
|
|
try {
|
|
$this->claimCheckerManager->check($data, $this->mandatoryClaims);
|
|
} catch (MissingMandatoryClaimException|InvalidClaimException $e) {
|
|
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, $e->getMessage(), $e, $data);
|
|
} catch (\Throwable $e) {
|
|
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Unable to load the token', $e, $data);
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function loadJWS(string $token): array
|
|
{
|
|
$payload = null;
|
|
$data = null;
|
|
$signature = null;
|
|
try {
|
|
$jws = $this->jwsLoader->loadAndVerifyWithKeySet($token, $this->signatureKeyset, $signature);
|
|
} catch (\Throwable $e) {
|
|
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid token. The token cannot be loaded or the signature cannot be verified.');
|
|
}
|
|
if ($signature !== 0) {
|
|
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid token. The token shall contain only one signature.');
|
|
}
|
|
|
|
$payload = $jws->getPayload();
|
|
if (!$payload) {
|
|
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid payload. The token shall contain claims.');
|
|
}
|
|
|
|
$data = json_decode($payload, true);
|
|
if (!is_array($data)) {
|
|
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid payload. The token shall contain claims.');
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
private function loadJWE(string $token): string
|
|
{
|
|
if (!$this->jweLoader) {
|
|
return $token;
|
|
}
|
|
|
|
$recipient = null;
|
|
try {
|
|
$jwe = $this->jweLoader->loadAndDecryptWithKeySet($token, $this->encryptionKeyset, $recipient);
|
|
} catch (\Throwable $e) {
|
|
if ($this->continueOnDecryptionFailure === true) {
|
|
return $token;
|
|
}
|
|
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid token. The token cannot be decrypted.', $e);
|
|
}
|
|
$token = $jwe->getPayload();
|
|
if (!$token || $recipient !== 0) {
|
|
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid token. The token has no valid content.');
|
|
}
|
|
|
|
return $token;
|
|
}
|
|
}
|