first commit

This commit is contained in:
2024-11-05 12:22:50 +01:00
commit e5682a3912
19641 changed files with 2948548 additions and 0 deletions

View File

@@ -0,0 +1,592 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT;
use DateTimeImmutable;
use Lcobucci\JWT\Claim\Factory as ClaimFactory;
use Lcobucci\JWT\Parsing\Encoder;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Token\DataSet;
use Lcobucci\JWT\Token\RegisteredClaimGiven;
use Lcobucci\JWT\Token\RegisteredClaims;
use function array_diff;
use function array_filter;
use function array_key_exists;
use function array_merge;
use function array_shift;
use function count;
use function current;
use function in_array;
use function is_array;
use function is_bool;
use function trigger_error;
use const E_USER_DEPRECATED;
/**
* This class makes easier the token creation process
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 0.1.0
*/
class Builder
{
/**
* The token header
*
* @var array
*/
private $headers = ['typ'=> 'JWT', 'alg' => 'none'];
/**
* The token claim set
*
* @var array
*/
private $claims = [];
/**
* The data encoder
*
* @var Encoder
*/
private $encoder;
/**
* The factory of claims
*
* @var ClaimFactory
*/
private $claimFactory;
/**
* @var Signer|null
*/
private $signer;
/**
* @var Key|null
*/
private $key;
/**
* Initializes a new builder
*
* @param Encoder $encoder
* @param ClaimFactory $claimFactory
*/
public function __construct(
Encoder $encoder = null,
ClaimFactory $claimFactory = null
) {
$this->encoder = $encoder ?: new Encoder();
$this->claimFactory = $claimFactory ?: new ClaimFactory();
}
/**
* Configures the audience
*
* @deprecated This method has been wrongly added and doesn't exist on v4
* @see Builder::permittedFor()
*
* @param string $audience
* @param bool $replicateAsHeader
*
* @return Builder
*/
public function canOnlyBeUsedBy($audience, $replicateAsHeader = false)
{
return $this->permittedFor($audience, $replicateAsHeader);
}
/**
* Configures the audience
*
* @param list<string|bool> $audiences A list of audiences and, optionally, the instruction to replicate as header
*
* @return Builder
*/
public function permittedFor(...$audiences)
{
$claim = RegisteredClaims::AUDIENCE;
$replicateAsHeader = false;
if ($audiences !== [] && is_bool($audiences[count($audiences) - 1])) {
$replicateAsHeader = array_pop($audiences);
}
$audiences = array_filter($audiences, 'is_string');
$configured = array_key_exists($claim, $this->claims) ? $this->claims[$claim] : [];
$toAppend = array_diff($audiences, $configured);
return $this->setRegisteredClaim($claim, array_merge($configured, $toAppend), $replicateAsHeader);
}
/**
* Configures the audience
*
* @deprecated This method will be removed on v4
* @see Builder::permittedFor()
*
* @param string $audience
* @param boolean $replicateAsHeader
*
* @return Builder
*/
public function setAudience($audience, $replicateAsHeader = false)
{
return $this->permittedFor($audience, $replicateAsHeader);
}
/**
* Configures the expiration time
*
* @param int|DateTimeImmutable $expiration
* @param boolean $replicateAsHeader
*
* @return Builder
*/
public function expiresAt($expiration, $replicateAsHeader = false)
{
return $this->setRegisteredClaim('exp', $this->convertToDate($expiration), $replicateAsHeader);
}
/**
* @param int|DateTimeImmutable $value
*
* @return DateTimeImmutable
*/
private function convertToDate($value)
{
if (! $value instanceof DateTimeImmutable) {
trigger_error('Using integers for registered date claims is deprecated, please use DateTimeImmutable objects instead.', E_USER_DEPRECATED);
return new DateTimeImmutable('@' . $value);
}
return $value;
}
/**
* Configures the expiration time
*
* @deprecated This method will be removed on v4
* @see Builder::expiresAt()
*
* @param int|DateTimeImmutable $expiration
* @param boolean $replicateAsHeader
*
* @return Builder
*/
public function setExpiration($expiration, $replicateAsHeader = false)
{
return $this->expiresAt($expiration, $replicateAsHeader);
}
/**
* Configures the token id
*
* @param string $id
* @param boolean $replicateAsHeader
*
* @return Builder
*/
public function identifiedBy($id, $replicateAsHeader = false)
{
return $this->setRegisteredClaim('jti', (string) $id, $replicateAsHeader);
}
/**
* Configures the token id
*
* @deprecated This method will be removed on v4
* @see Builder::identifiedBy()
*
* @param string $id
* @param boolean $replicateAsHeader
*
* @return Builder
*/
public function setId($id, $replicateAsHeader = false)
{
return $this->identifiedBy($id, $replicateAsHeader);
}
/**
* Configures the time that the token was issued
*
* @param int|DateTimeImmutable $issuedAt
* @param boolean $replicateAsHeader
*
* @return Builder
*/
public function issuedAt($issuedAt, $replicateAsHeader = false)
{
return $this->setRegisteredClaim('iat', $this->convertToDate($issuedAt), $replicateAsHeader);
}
/**
* Configures the time that the token was issued
*
* @deprecated This method will be removed on v4
* @see Builder::issuedAt()
*
* @param int|DateTimeImmutable $issuedAt
* @param boolean $replicateAsHeader
*
* @return Builder
*/
public function setIssuedAt($issuedAt, $replicateAsHeader = false)
{
return $this->issuedAt($issuedAt, $replicateAsHeader);
}
/**
* Configures the issuer
*
* @param string $issuer
* @param boolean $replicateAsHeader
*
* @return Builder
*/
public function issuedBy($issuer, $replicateAsHeader = false)
{
return $this->setRegisteredClaim('iss', (string) $issuer, $replicateAsHeader);
}
/**
* Configures the issuer
*
* @deprecated This method will be removed on v4
* @see Builder::issuedBy()
*
* @param string $issuer
* @param boolean $replicateAsHeader
*
* @return Builder
*/
public function setIssuer($issuer, $replicateAsHeader = false)
{
return $this->issuedBy($issuer, $replicateAsHeader);
}
/**
* Configures the time before which the token cannot be accepted
*
* @param int|DateTimeImmutable $notBefore
* @param boolean $replicateAsHeader
*
* @return Builder
*/
public function canOnlyBeUsedAfter($notBefore, $replicateAsHeader = false)
{
return $this->setRegisteredClaim('nbf', $this->convertToDate($notBefore), $replicateAsHeader);
}
/**
* Configures the time before which the token cannot be accepted
*
* @deprecated This method will be removed on v4
* @see Builder::canOnlyBeUsedAfter()
*
* @param int|DateTimeImmutable $notBefore
* @param boolean $replicateAsHeader
*
* @return Builder
*/
public function setNotBefore($notBefore, $replicateAsHeader = false)
{
return $this->canOnlyBeUsedAfter($notBefore, $replicateAsHeader);
}
/**
* Configures the subject
*
* @param string $subject
* @param boolean $replicateAsHeader
*
* @return Builder
*/
public function relatedTo($subject, $replicateAsHeader = false)
{
return $this->setRegisteredClaim('sub', (string) $subject, $replicateAsHeader);
}
/**
* Configures the subject
*
* @deprecated This method will be removed on v4
* @see Builder::relatedTo()
*
* @param string $subject
* @param boolean $replicateAsHeader
*
* @return Builder
*/
public function setSubject($subject, $replicateAsHeader = false)
{
return $this->relatedTo($subject, $replicateAsHeader);
}
/**
* Configures a registered claim
*
* @param string $name
* @param mixed $value
* @param boolean $replicate
*
* @return Builder
*/
protected function setRegisteredClaim($name, $value, $replicate)
{
$this->configureClaim($name, $value);
if ($replicate) {
trigger_error('Replicating claims as headers is deprecated and will removed from v4.0. Please manually set the header if you need it replicated.', E_USER_DEPRECATED);
$this->headers[$name] = $value;
}
return $this;
}
/**
* Configures a header item
*
* @param string $name
* @param mixed $value
*
* @return Builder
*/
public function withHeader($name, $value)
{
$this->headers[(string) $name] = $value;
return $this;
}
/**
* Configures a header item
*
* @deprecated This method will be removed on v4
* @see Builder::withHeader()
*
* @param string $name
* @param mixed $value
*
* @return Builder
*/
public function setHeader($name, $value)
{
return $this->withHeader($name, $value);
}
/**
* Configures a claim item
*
* @deprecated This method has been wrongly added and doesn't exist on v4
* @see Builder::withClaim()
*
* @param string $name
* @param mixed $value
*
* @return Builder
*/
public function with($name, $value)
{
return $this->withClaim($name, $value);
}
/**
* @param string $name
* @param mixed $value
*
* @return Builder
*/
private function configureClaim($name, $value)
{
$this->claims[(string) $name] = $value;
return $this;
}
/**
* Configures a claim item
*
* @param string $name
* @param mixed $value
*
* @return Builder
*
* @throws RegisteredClaimGiven
*/
public function withClaim($name, $value)
{
if (in_array($name, RegisteredClaims::ALL, true)) {
trigger_error('The use of the method "withClaim" is deprecated for registered claims. Please use dedicated method instead.', E_USER_DEPRECATED);
}
return $this->forwardCallToCorrectClaimMethod($name, $value);
}
private function forwardCallToCorrectClaimMethod($name, $value)
{
switch ($name) {
case RegisteredClaims::ID:
return $this->identifiedBy($value);
case RegisteredClaims::EXPIRATION_TIME:
return $this->expiresAt($value);
case RegisteredClaims::NOT_BEFORE:
return $this->canOnlyBeUsedAfter($value);
case RegisteredClaims::ISSUED_AT:
return $this->issuedAt($value);
case RegisteredClaims::ISSUER:
return $this->issuedBy($value);
case RegisteredClaims::AUDIENCE:
return $this->permittedFor($value);
default:
return $this->configureClaim($name, $value);
}
}
/**
* Configures a claim item
*
* @deprecated This method will be removed on v4
* @see Builder::withClaim()
*
* @param string $name
* @param mixed $value
*
* @return Builder
*/
public function set($name, $value)
{
return $this->forwardCallToCorrectClaimMethod($name, $value);
}
/**
* Signs the data
*
* @deprecated This method will be removed on v4
* @see Builder::getToken()
*
* @param Signer $signer
* @param Key|string $key
*
* @return Builder
*/
public function sign(Signer $signer, $key)
{
if (! $key instanceof Key) {
trigger_error('Implicit conversion of keys from strings is deprecated. Please use InMemory or LocalFileReference classes.', E_USER_DEPRECATED);
$key = new Key($key);
}
$this->signer = $signer;
$this->key = $key;
return $this;
}
/**
* Removes the signature from the builder
*
* @deprecated This method will be removed on v4
* @see Builder::getToken()
*
* @return Builder
*/
public function unsign()
{
$this->signer = null;
$this->key = null;
return $this;
}
/**
* Returns the resultant token
*
* @return Token
*/
public function getToken(Signer $signer = null, Key $key = null)
{
if ($signer === null || $key === null) {
trigger_error('Not specifying the signer and key to Builder#getToken() is deprecated. Please move the arguments from Builder#sign() to Builder#getToken().', E_USER_DEPRECATED);
}
$signer = $signer ?: $this->signer;
$key = $key ?: $this->key;
if ($signer instanceof Signer) {
$signer->modifyHeader($this->headers);
}
$headers = new DataSet(
$this->headers,
$this->encoder->base64UrlEncode($this->encoder->jsonEncode($this->convertItems($this->headers)))
);
$claims = new DataSet(
$this->claims,
$this->encoder->base64UrlEncode($this->encoder->jsonEncode($this->convertItems($this->claims)))
);
return new Token(
$headers,
$claims,
$this->createSignature($headers->toString() . '.' . $claims->toString(), $signer, $key),
['', ''],
$this->claimFactory
);
}
/**
* @param array<string, mixed> $items
*
* @return array<string, mixed>
*/
private function convertItems(array $items)
{
foreach (RegisteredClaims::DATE_CLAIMS as $name) {
if (! array_key_exists($name, $items) || ! $items[$name] instanceof DateTimeImmutable) {
continue;
}
$items[$name] = $items[$name]->getTimestamp();
}
$audience = RegisteredClaims::AUDIENCE;
if (array_key_exists($audience, $items) && is_array($items[$audience]) && count($items[$audience]) === 1) {
$items[$audience] = current($items[$audience]);
}
return $items;
}
/**
* @param string $payload
*
* @return Signature
*/
private function createSignature($payload, Signer $signer = null, Key $key = null)
{
if ($signer === null || $key === null) {
return Signature::fromEmptyData();
}
$hash = $signer->sign($payload, $key)->hash();
return new Signature($hash, $this->encoder->base64UrlEncode($hash));
}
}

View File

@@ -0,0 +1,40 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT;
use JsonSerializable;
/**
* Basic interface for token claims
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 2.0.0
*/
interface Claim extends JsonSerializable
{
/**
* Returns the claim name
*
* @return string
*/
public function getName();
/**
* Returns the claim value
*
* @return mixed
*/
public function getValue();
/**
* Returns the string representation of the claim
*
* @return string
*/
public function __toString();
}

View File

@@ -0,0 +1,75 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT\Claim;
use Lcobucci\JWT\Claim;
/**
* The default claim
*
* @deprecated This class will be removed on v4
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 2.0.0
*/
class Basic implements Claim
{
/**
* @var string
*/
private $name;
/**
* @var mixed
*/
private $value;
/**
* Initializes the claim
*
* @param string $name
* @param mixed $value
*/
public function __construct($name, $value)
{
$this->name = $name;
$this->value = $value;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->name;
}
/**
* {@inheritdoc}
*/
public function getValue()
{
return $this->value;
}
/**
* {@inheritdoc}
*/
public function jsonSerialize()
{
return $this->value;
}
/**
* {@inheritdoc}
*/
public function __toString()
{
return (string) $this->value;
}
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT\Claim;
use Lcobucci\JWT\Claim;
use Lcobucci\JWT\ValidationData;
/**
* Validatable claim that checks if value is strictly equals to the given data
*
* @deprecated This class will be removed on v4
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 2.0.0
*/
class EqualsTo extends Basic implements Claim, Validatable
{
/**
* {@inheritdoc}
*/
public function validate(ValidationData $data)
{
if ($data->has($this->getName())) {
return $this->getValue() === $data->get($this->getName());
}
return true;
}
}

View File

@@ -0,0 +1,131 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT\Claim;
use DateTimeImmutable;
use Lcobucci\JWT\Claim;
use Lcobucci\JWT\Token\RegisteredClaims;
use function current;
use function in_array;
use function is_array;
/**
* Class that create claims
*
* @deprecated This class will be removed on v4
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 2.0.0
*/
class Factory
{
/**
* The list of claim callbacks
*
* @var array
*/
private $callbacks;
/**
* Initializes the factory, registering the default callbacks
*
* @param array $callbacks
*/
public function __construct(array $callbacks = [])
{
$this->callbacks = array_merge(
[
'iat' => [$this, 'createLesserOrEqualsTo'],
'nbf' => [$this, 'createLesserOrEqualsTo'],
'exp' => [$this, 'createGreaterOrEqualsTo'],
'iss' => [$this, 'createEqualsTo'],
'aud' => [$this, 'createEqualsTo'],
'sub' => [$this, 'createEqualsTo'],
'jti' => [$this, 'createEqualsTo']
],
$callbacks
);
}
/**
* Create a new claim
*
* @param string $name
* @param mixed $value
*
* @return Claim
*/
public function create($name, $value)
{
if ($value instanceof DateTimeImmutable && in_array($name, RegisteredClaims::DATE_CLAIMS, true)) {
$value = $value->getTimestamp();
}
if ($name === RegisteredClaims::AUDIENCE && is_array($value)) {
$value = current($value);
}
if (!empty($this->callbacks[$name])) {
return call_user_func($this->callbacks[$name], $name, $value);
}
return $this->createBasic($name, $value);
}
/**
* Creates a claim that can be compared (greator or equals)
*
* @param string $name
* @param mixed $value
*
* @return GreaterOrEqualsTo
*/
private function createGreaterOrEqualsTo($name, $value)
{
return new GreaterOrEqualsTo($name, $value);
}
/**
* Creates a claim that can be compared (greator or equals)
*
* @param string $name
* @param mixed $value
*
* @return LesserOrEqualsTo
*/
private function createLesserOrEqualsTo($name, $value)
{
return new LesserOrEqualsTo($name, $value);
}
/**
* Creates a claim that can be compared (equals)
*
* @param string $name
* @param mixed $value
*
* @return EqualsTo
*/
private function createEqualsTo($name, $value)
{
return new EqualsTo($name, $value);
}
/**
* Creates a basic claim
*
* @param string $name
* @param mixed $value
*
* @return Basic
*/
private function createBasic($name, $value)
{
return new Basic($name, $value);
}
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT\Claim;
use Lcobucci\JWT\Claim;
use Lcobucci\JWT\ValidationData;
/**
* Validatable claim that checks if value is greater or equals the given data
*
* @deprecated This class will be removed on v4
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 2.0.0
*/
class GreaterOrEqualsTo extends Basic implements Claim, Validatable
{
/**
* {@inheritdoc}
*/
public function validate(ValidationData $data)
{
if ($data->has($this->getName())) {
return $this->getValue() >= $data->get($this->getName());
}
return true;
}
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT\Claim;
use Lcobucci\JWT\Claim;
use Lcobucci\JWT\ValidationData;
/**
* Validatable claim that checks if value is lesser or equals to the given data
*
* @deprecated This class will be removed on v4
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 2.0.0
*/
class LesserOrEqualsTo extends Basic implements Claim, Validatable
{
/**
* {@inheritdoc}
*/
public function validate(ValidationData $data)
{
if ($data->has($this->getName())) {
return $this->getValue() <= $data->get($this->getName());
}
return true;
}
}

View File

@@ -0,0 +1,30 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT\Claim;
use Lcobucci\JWT\ValidationData;
/**
* Basic interface for validatable token claims
*
* @deprecated This interface will be removed on v4
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 2.0.0
*/
interface Validatable
{
/**
* Returns if claim is valid according with given data
*
* @param ValidationData $data
*
* @return boolean
*/
public function validate(ValidationData $data);
}

View File

@@ -0,0 +1,178 @@
<?php
namespace Lcobucci\JWT;
use Closure;
use Lcobucci\JWT\Parsing\Decoder;
use Lcobucci\JWT\Parsing\Encoder;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Signer\None;
use Lcobucci\JWT\Validation\Constraint;
/**
* Configuration container for the JWT Builder and Parser
*
* Serves like a small DI container to simplify the creation and usage
* of the objects.
*/
final class Configuration
{
/** @var Parser */
private $parser;
/** @var Signer */
private $signer;
/** @var Key */
private $signingKey;
/** @var Key */
private $verificationKey;
/** @var Validator */
private $validator;
/** @var Closure(): Builder */
private $builderFactory;
/** @var Constraint[] */
private $validationConstraints = [];
private function __construct(
Signer $signer,
Key $signingKey,
Key $verificationKey,
Encoder $encoder = null,
Decoder $decoder = null
) {
$this->signer = $signer;
$this->signingKey = $signingKey;
$this->verificationKey = $verificationKey;
$this->parser = new Parser($decoder ?: new Decoder());
$this->validator = new Validation\Validator();
$this->builderFactory = static function () use ($encoder) {
return new Builder($encoder ?: new Encoder());
};
}
/** @return self */
public static function forAsymmetricSigner(
Signer $signer,
Key $signingKey,
Key $verificationKey,
Encoder $encoder = null,
Decoder $decoder = null
) {
return new self(
$signer,
$signingKey,
$verificationKey,
$encoder,
$decoder
);
}
/** @return self */
public static function forSymmetricSigner(
Signer $signer,
Key $key,
Encoder $encoder = null,
Decoder $decoder = null
) {
return new self(
$signer,
$key,
$key,
$encoder,
$decoder
);
}
/** @return self */
public static function forUnsecuredSigner(
Encoder $encoder = null,
Decoder $decoder = null
) {
$key = InMemory::plainText('');
return new self(
new None(),
$key,
$key,
$encoder,
$decoder
);
}
/** @param callable(): Builder $builderFactory */
public function setBuilderFactory(callable $builderFactory)
{
if (! $builderFactory instanceof Closure) {
$builderFactory = static function() use ($builderFactory) {
return $builderFactory();
};
}
$this->builderFactory = $builderFactory;
}
/** @return Builder */
public function builder()
{
$factory = $this->builderFactory;
return $factory();
}
/** @return Parser */
public function parser()
{
return $this->parser;
}
public function setParser(Parser $parser)
{
$this->parser = $parser;
}
/** @return Signer */
public function signer()
{
return $this->signer;
}
/** @return Key */
public function signingKey()
{
return $this->signingKey;
}
/** @return Key */
public function verificationKey()
{
return $this->verificationKey;
}
/** @return Validator */
public function validator()
{
return $this->validator;
}
public function setValidator(Validator $validator)
{
$this->validator = $validator;
}
/** @return Constraint[] */
public function validationConstraints()
{
return $this->validationConstraints;
}
public function setValidationConstraints(Constraint ...$validationConstraints)
{
$this->validationConstraints = $validationConstraints;
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Lcobucci\JWT\Encoding;
use JsonException;
use Lcobucci\JWT\Exception;
use RuntimeException;
final class CannotDecodeContent extends RuntimeException implements Exception
{
/**
* @param JsonException $previous
*
* @return self
*/
public static function jsonIssues(JsonException $previous)
{
return new self('Error while decoding from JSON', 0, $previous);
}
/** @return self */
public static function invalidBase64String()
{
return new self('Error while decoding from Base64Url, invalid base64 characters detected');
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Lcobucci\JWT\Encoding;
use JsonException;
use Lcobucci\JWT\Exception;
use RuntimeException;
final class CannotEncodeContent extends RuntimeException implements Exception
{
/**
* @param JsonException $previous
*
* @return self
*/
public static function jsonIssues(JsonException $previous)
{
return new self('Error while encoding to JSON', 0, $previous);
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Lcobucci\JWT;
if (PHP_MAJOR_VERSION === 5) {
interface Exception
{
}
} else {
interface Exception extends \Throwable
{
}
}

View File

@@ -0,0 +1,175 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT;
use DateTimeImmutable;
use InvalidArgumentException;
use Lcobucci\JWT\Parsing\Decoder;
use Lcobucci\JWT\Token\DataSet;
use Lcobucci\JWT\Token\InvalidTokenStructure;
use Lcobucci\JWT\Token\RegisteredClaims;
use Lcobucci\JWT\Token\UnsupportedHeaderFound;
use RuntimeException;
use function array_key_exists;
use function is_array;
/**
* This class parses the JWT strings and convert them into tokens
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 0.1.0
*/
class Parser
{
/**
* The data decoder
*
* @var Decoder
*/
private $decoder;
/**
* Initializes the object
*
* @param Decoder $decoder
*/
public function __construct(Decoder $decoder = null)
{
$this->decoder = $decoder ?: new Decoder();
}
/**
* Parses the JWT and returns a token
*
* @param string $jwt
*
* @return Token
*
* @throws InvalidArgumentException When JWT is not a string or is invalid.
* @throws RuntimeException When something goes wrong while decoding
*/
public function parse($jwt)
{
$data = $this->splitJwt($jwt);
$header = $this->parseHeader($data[0]);
$claims = $this->parseClaims($data[1]);
$signature = $this->parseSignature($header, $data[2]);
foreach ($claims as $name => $value) {
if (isset($header[$name])) {
$header[$name] = $value;
}
}
return new Token(
new DataSet($header, $data[0]),
new DataSet($claims, $data[1]),
$signature,
['', '']
);
}
/**
* Splits the JWT string into an array
*
* @param string $jwt
*
* @return array
*
* @throws InvalidArgumentException When JWT is not a string or is invalid
*/
protected function splitJwt($jwt)
{
if (!is_string($jwt)) {
throw InvalidTokenStructure::missingOrNotEnoughSeparators();
}
$data = explode('.', $jwt);
if (count($data) != 3) {
throw InvalidTokenStructure::missingOrNotEnoughSeparators();
}
return $data;
}
/**
* Parses the header from a string
*
* @param string $data
*
* @return array
*
* @throws UnsupportedHeaderFound When an invalid header is informed
*/
protected function parseHeader($data)
{
$header = (array) $this->decoder->jsonDecode($this->decoder->base64UrlDecode($data));
if (isset($header['enc'])) {
throw UnsupportedHeaderFound::encryption();
}
return $this->convertItems($header);
}
/**
* Parses the claim set from a string
*
* @param string $data
*
* @return array
*/
protected function parseClaims($data)
{
$claims = (array) $this->decoder->jsonDecode($this->decoder->base64UrlDecode($data));
return $this->convertItems($claims);
}
/**
* @param array<string, mixed> $items
*
* @return array<string, mixed>
*/
private function convertItems(array $items)
{
foreach (RegisteredClaims::DATE_CLAIMS as $name) {
if (! array_key_exists($name, $items)) {
continue;
}
$items[$name] = new DateTimeImmutable('@' . ((int) $items[$name]));
}
if (array_key_exists(RegisteredClaims::AUDIENCE, $items) && ! is_array($items[RegisteredClaims::AUDIENCE])) {
$items[RegisteredClaims::AUDIENCE] = [$items[RegisteredClaims::AUDIENCE]];
}
return $items;
}
/**
* Returns the signature from given data
*
* @param array $header
* @param string $data
*
* @return Signature
*/
protected function parseSignature(array $header, $data)
{
if ($data == '' || !isset($header['alg']) || $header['alg'] == 'none') {
return Signature::fromEmptyData();
}
$hash = $this->decoder->base64UrlDecode($data);
return new Signature($hash, $data);
}
}

View File

@@ -0,0 +1,70 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT\Parsing;
use JsonException;
use Lcobucci\JWT\Encoding\CannotDecodeContent;
use RuntimeException;
use function json_decode;
use function json_last_error;
use function json_last_error_msg;
/**
* Class that decodes data according with the specs of RFC-4648
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 0.1.0
*
* @link http://tools.ietf.org/html/rfc4648#section-5
*/
class Decoder
{
/**
* Decodes from JSON, validating the errors (will return an associative array
* instead of objects)
*
* @param string $json
* @return mixed
*
* @throws RuntimeException When something goes wrong while decoding
*/
public function jsonDecode($json)
{
if (PHP_VERSION_ID < 70300) {
$data = json_decode($json);
if (json_last_error() != JSON_ERROR_NONE) {
throw CannotDecodeContent::jsonIssues(new JsonException(json_last_error_msg()));
}
return $data;
}
try {
return json_decode($json, false, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $exception) {
throw CannotDecodeContent::jsonIssues($exception);
}
}
/**
* Decodes from base64url
*
* @param string $data
* @return string
*/
public function base64UrlDecode($data)
{
if ($remainder = strlen($data) % 4) {
$data .= str_repeat('=', 4 - $remainder);
}
return base64_decode(strtr($data, '-_', '+/'));
}
}

View File

@@ -0,0 +1,65 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT\Parsing;
use JsonException;
use Lcobucci\JWT\Encoding\CannotEncodeContent;
use RuntimeException;
use function json_encode;
use function json_last_error;
use function json_last_error_msg;
/**
* Class that encodes data according with the specs of RFC-4648
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 0.1.0
*
* @link http://tools.ietf.org/html/rfc4648#section-5
*/
class Encoder
{
/**
* Encodes to JSON, validating the errors
*
* @param mixed $data
* @return string
*
* @throws RuntimeException When something goes wrong while encoding
*/
public function jsonEncode($data)
{
if (PHP_VERSION_ID < 70300) {
$json = json_encode($data);
if (json_last_error() != JSON_ERROR_NONE) {
throw CannotEncodeContent::jsonIssues(new JsonException(json_last_error_msg()));
}
return $json;
}
try {
return json_encode($data, JSON_THROW_ON_ERROR);
} catch (JsonException $exception) {
throw CannotEncodeContent::jsonIssues($exception);
}
}
/**
* Encodes to base64url
*
* @param string $data
* @return string
*/
public function base64UrlEncode($data)
{
return str_replace('=', '', strtr(base64_encode($data), '+/', '-_'));
}
}

View File

@@ -0,0 +1,87 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT;
use Lcobucci\JWT\Signer\Key;
/**
* This class represents a token signature
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 0.1.0
*/
class Signature
{
/**
* The resultant hash
*
* @var string
*/
protected $hash;
/** @var string */
private $encoded;
/**
* Initializes the object
*
* @param string $hash
* @param string $encoded
*/
public function __construct($hash, $encoded = '')
{
$this->hash = $hash;
$this->encoded = $encoded;
}
/** @return self */
public static function fromEmptyData()
{
return new self('', '');
}
/**
* Verifies if the current hash matches with with the result of the creation of
* a new signature with given data
*
* @param Signer $signer
* @param string $payload
* @param Key|string $key
*
* @return boolean
*/
public function verify(Signer $signer, $payload, $key)
{
return $signer->verify($this->hash, $payload, $key);
}
/**
* Returns the current hash as a string representation of the signature
*
* @deprecated This method has been removed from the public API in v4
* @see Signature::hash()
*
* @return string
*/
public function __toString()
{
return $this->hash;
}
/** @return string */
public function hash()
{
return $this->hash;
}
/** @return string */
public function toString()
{
return $this->encoded;
}
}

View File

@@ -0,0 +1,59 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT;
use InvalidArgumentException;
use Lcobucci\JWT\Signer\Key;
/**
* Basic interface for token signers
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 0.1.0
*/
interface Signer
{
/**
* Returns the algorithm id
*
* @return string
*/
public function getAlgorithmId();
/**
* Apply changes on headers according with algorithm
*
* @param array $headers
*/
public function modifyHeader(array &$headers);
/**
* Returns a signature for given data
*
* @param string $payload
* @param Key|string $key
*
* @return Signature
*
* @throws InvalidArgumentException When given key is invalid
*/
public function sign($payload, $key);
/**
* Returns if the expected hash matches with the data and key
*
* @param string $expected
* @param string $payload
* @param Key|string $key
*
* @return boolean
*
* @throws InvalidArgumentException When given key is invalid
*/
public function verify($expected, $payload, $key);
}

View File

@@ -0,0 +1,89 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT\Signer;
use Lcobucci\JWT\Signature;
use Lcobucci\JWT\Signer;
use function trigger_error;
use const E_USER_DEPRECATED;
/**
* Base class for signers
*
* @deprecated This class will be removed on v4
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 0.1.0
*/
abstract class BaseSigner implements Signer
{
/**
* {@inheritdoc}
*/
public function modifyHeader(array &$headers)
{
$headers['alg'] = $this->getAlgorithmId();
}
/**
* {@inheritdoc}
*/
public function sign($payload, $key)
{
return new Signature($this->createHash($payload, $this->getKey($key)));
}
/**
* {@inheritdoc}
*/
public function verify($expected, $payload, $key)
{
return $this->doVerify($expected, $payload, $this->getKey($key));
}
/**
* @param Key|string $key
*
* @return Key
*/
private function getKey($key)
{
if (is_string($key)) {
trigger_error('Implicit conversion of keys from strings is deprecated. Please use InMemory or LocalFileReference classes.', E_USER_DEPRECATED);
$key = new Key($key);
}
return $key;
}
/**
* Creates a hash with the given data
*
* @internal
*
* @param string $payload
* @param Key $key
*
* @return string
*/
abstract public function createHash($payload, Key $key);
/**
* Performs the signature verification
*
* @internal
*
* @param string $expected
* @param string $payload
* @param Key $key
*
* @return boolean
*/
abstract public function doVerify($expected, $payload, Key $key);
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Lcobucci\JWT\Signer;
use InvalidArgumentException;
use Lcobucci\JWT\Exception;
final class CannotSignPayload extends InvalidArgumentException implements Exception
{
/**
* @pararm string $error
*
* @return self
*/
public static function errorHappened($error)
{
return new self('There was an error while creating the signature: ' . $error);
}
}

View File

@@ -0,0 +1,69 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT\Signer;
use Lcobucci\JWT\Signer\Ecdsa\MultibyteStringConverter;
use Lcobucci\JWT\Signer\Ecdsa\SignatureConverter;
use const OPENSSL_KEYTYPE_EC;
/**
* Base class for ECDSA signers
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 2.1.0
*/
abstract class Ecdsa extends OpenSSL
{
/**
* @var SignatureConverter
*/
private $converter;
public function __construct(SignatureConverter $converter = null)
{
$this->converter = $converter ?: new MultibyteStringConverter();
}
/**
* {@inheritdoc}
*/
public function createHash($payload, Key $key)
{
return $this->converter->fromAsn1(
parent::createHash($payload, $key),
$this->getKeyLength()
);
}
/**
* {@inheritdoc}
*/
public function doVerify($expected, $payload, Key $key)
{
return parent::doVerify(
$this->converter->toAsn1($expected, $this->getKeyLength()),
$payload,
$key
);
}
/**
* Returns the length of each point in the signature, so that we can calculate and verify R and S points properly
*
* @internal
*/
abstract public function getKeyLength();
/**
* {@inheritdoc}
*/
final public function getKeyType()
{
return OPENSSL_KEYTYPE_EC;
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Lcobucci\JWT\Signer\Ecdsa;
use InvalidArgumentException;
use Lcobucci\JWT\Exception;
final class ConversionFailed extends InvalidArgumentException implements Exception
{
/** @return self */
public static function invalidLength()
{
return new self('Invalid signature length.');
}
/** @return self */
public static function incorrectStartSequence()
{
return new self('Invalid data. Should start with a sequence.');
}
/** @return self */
public static function integerExpected()
{
return new self('Invalid data. Should contain an integer.');
}
}

View File

@@ -0,0 +1,133 @@
<?php
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2018 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*
* @link https://github.com/web-token/jwt-framework/blob/v1.2/src/Component/Core/Util/ECSignature.php
*/
namespace Lcobucci\JWT\Signer\Ecdsa;
use function bin2hex;
use function dechex;
use function hex2bin;
use function hexdec;
use function mb_strlen;
use function mb_substr;
use function str_pad;
use const STR_PAD_LEFT;
/**
* ECDSA signature converter using ext-mbstring
*
* @internal
*/
final class MultibyteStringConverter implements SignatureConverter
{
const ASN1_SEQUENCE = '30';
const ASN1_INTEGER = '02';
const ASN1_MAX_SINGLE_BYTE = 128;
const ASN1_LENGTH_2BYTES = '81';
const ASN1_BIG_INTEGER_LIMIT = '7f';
const ASN1_NEGATIVE_INTEGER = '00';
const BYTE_SIZE = 2;
public function toAsn1($signature, $length)
{
$signature = bin2hex($signature);
if (self::octetLength($signature) !== $length) {
throw ConversionFailed::invalidLength();
}
$pointR = self::preparePositiveInteger(mb_substr($signature, 0, $length, '8bit'));
$pointS = self::preparePositiveInteger(mb_substr($signature, $length, null, '8bit'));
$lengthR = self::octetLength($pointR);
$lengthS = self::octetLength($pointS);
$totalLength = $lengthR + $lengthS + self::BYTE_SIZE + self::BYTE_SIZE;
$lengthPrefix = $totalLength > self::ASN1_MAX_SINGLE_BYTE ? self::ASN1_LENGTH_2BYTES : '';
$asn1 = hex2bin(
self::ASN1_SEQUENCE
. $lengthPrefix . dechex($totalLength)
. self::ASN1_INTEGER . dechex($lengthR) . $pointR
. self::ASN1_INTEGER . dechex($lengthS) . $pointS
);
return $asn1;
}
private static function octetLength($data)
{
return (int) (mb_strlen($data, '8bit') / self::BYTE_SIZE);
}
private static function preparePositiveInteger($data)
{
if (mb_substr($data, 0, self::BYTE_SIZE, '8bit') > self::ASN1_BIG_INTEGER_LIMIT) {
return self::ASN1_NEGATIVE_INTEGER . $data;
}
while (mb_substr($data, 0, self::BYTE_SIZE, '8bit') === self::ASN1_NEGATIVE_INTEGER
&& mb_substr($data, 2, self::BYTE_SIZE, '8bit') <= self::ASN1_BIG_INTEGER_LIMIT) {
$data = mb_substr($data, 2, null, '8bit');
}
return $data;
}
public function fromAsn1($signature, $length)
{
$message = bin2hex($signature);
$position = 0;
if (self::readAsn1Content($message, $position, self::BYTE_SIZE) !== self::ASN1_SEQUENCE) {
throw ConversionFailed::incorrectStartSequence();
}
if (self::readAsn1Content($message, $position, self::BYTE_SIZE) === self::ASN1_LENGTH_2BYTES) {
$position += self::BYTE_SIZE;
}
$pointR = self::retrievePositiveInteger(self::readAsn1Integer($message, $position));
$pointS = self::retrievePositiveInteger(self::readAsn1Integer($message, $position));
$points = hex2bin(str_pad($pointR, $length, '0', STR_PAD_LEFT) . str_pad($pointS, $length, '0', STR_PAD_LEFT));
return $points;
}
private static function readAsn1Content($message, &$position, $length)
{
$content = mb_substr($message, $position, $length, '8bit');
$position += $length;
return $content;
}
private static function readAsn1Integer($message, &$position)
{
if (self::readAsn1Content($message, $position, self::BYTE_SIZE) !== self::ASN1_INTEGER) {
throw ConversionFailed::integerExpected();
}
$length = (int) hexdec(self::readAsn1Content($message, $position, self::BYTE_SIZE));
return self::readAsn1Content($message, $position, $length * self::BYTE_SIZE);
}
private static function retrievePositiveInteger($data)
{
while (mb_substr($data, 0, self::BYTE_SIZE, '8bit') === self::ASN1_NEGATIVE_INTEGER
&& mb_substr($data, 2, self::BYTE_SIZE, '8bit') > self::ASN1_BIG_INTEGER_LIMIT) {
$data = mb_substr($data, 2, null, '8bit');
}
return $data;
}
}

View File

@@ -0,0 +1,43 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT\Signer\Ecdsa;
use Lcobucci\JWT\Signer\Ecdsa;
/**
* Signer for ECDSA SHA-256
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 2.1.0
*/
class Sha256 extends Ecdsa
{
/**
* {@inheritdoc}
*/
public function getAlgorithmId()
{
return 'ES256';
}
/**
* {@inheritdoc}
*/
public function getAlgorithm()
{
return 'sha256';
}
/**
* {@inheritdoc}
*/
public function getKeyLength()
{
return 64;
}
}

View File

@@ -0,0 +1,43 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT\Signer\Ecdsa;
use Lcobucci\JWT\Signer\Ecdsa;
/**
* Signer for ECDSA SHA-384
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 2.1.0
*/
class Sha384 extends Ecdsa
{
/**
* {@inheritdoc}
*/
public function getAlgorithmId()
{
return 'ES384';
}
/**
* {@inheritdoc}
*/
public function getAlgorithm()
{
return 'sha384';
}
/**
* {@inheritdoc}
*/
public function getKeyLength()
{
return 96;
}
}

View File

@@ -0,0 +1,43 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT\Signer\Ecdsa;
use Lcobucci\JWT\Signer\Ecdsa;
/**
* Signer for ECDSA SHA-512
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 2.1.0
*/
class Sha512 extends Ecdsa
{
/**
* {@inheritdoc}
*/
public function getAlgorithmId()
{
return 'ES512';
}
/**
* {@inheritdoc}
*/
public function getAlgorithm()
{
return 'sha512';
}
/**
* {@inheritdoc}
*/
public function getKeyLength()
{
return 132;
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Lcobucci\JWT\Signer\Ecdsa;
/**
* Manipulates the result of a ECDSA signature (points R and S) according to the
* JWA specs.
*
* OpenSSL creates a signature using the ASN.1 format and, according the JWA specs,
* the signature for JWTs must be the concatenated values of points R and S (in
* big-endian octet order).
*
* @internal
*
* @see https://tools.ietf.org/html/rfc7518#page-9
* @see https://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One
*/
interface SignatureConverter
{
/**
* Converts the signature generated by OpenSSL into what JWA defines
*
* @param string $signature
* @param int $length
*
* @return string
*/
public function fromAsn1($signature, $length);
/**
* Converts the JWA signature into something OpenSSL understands
*
* @param string $points
* @param int $length
*
* @return string
*/
public function toAsn1($points, $length);
}

View File

@@ -0,0 +1,46 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT\Signer;
/**
* Base class for hmac signers
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 0.1.0
*/
abstract class Hmac extends BaseSigner
{
/**
* {@inheritdoc}
*/
public function createHash($payload, Key $key)
{
return hash_hmac($this->getAlgorithm(), $payload, $key->getContent(), true);
}
/**
* {@inheritdoc}
*/
public function doVerify($expected, $payload, Key $key)
{
if (!is_string($expected)) {
return false;
}
return hash_equals($expected, $this->createHash($payload, $key));
}
/**
* Returns the algorithm name
*
* @internal
*
* @return string
*/
abstract public function getAlgorithm();
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT\Signer\Hmac;
use Lcobucci\JWT\Signer\Hmac;
/**
* Signer for HMAC SHA-256
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 0.1.0
*/
class Sha256 extends Hmac
{
/**
* {@inheritdoc}
*/
public function getAlgorithmId()
{
return 'HS256';
}
/**
* {@inheritdoc}
*/
public function getAlgorithm()
{
return 'sha256';
}
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT\Signer\Hmac;
use Lcobucci\JWT\Signer\Hmac;
/**
* Signer for HMAC SHA-384
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 0.1.0
*/
class Sha384 extends Hmac
{
/**
* {@inheritdoc}
*/
public function getAlgorithmId()
{
return 'HS384';
}
/**
* {@inheritdoc}
*/
public function getAlgorithm()
{
return 'sha384';
}
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT\Signer\Hmac;
use Lcobucci\JWT\Signer\Hmac;
/**
* Signer for HMAC SHA-512
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 0.1.0
*/
class Sha512 extends Hmac
{
/**
* {@inheritdoc}
*/
public function getAlgorithmId()
{
return 'HS512';
}
/**
* {@inheritdoc}
*/
public function getAlgorithm()
{
return 'sha512';
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Lcobucci\JWT\Signer;
use InvalidArgumentException;
use Lcobucci\JWT\Exception;
final class InvalidKeyProvided extends InvalidArgumentException implements Exception
{
/**
* @param string $details
*
* @return self
*/
public static function cannotBeParsed($details)
{
return new self('It was not possible to parse your key, reason: ' . $details);
}
/** @return self */
public static function incompatibleKey()
{
return new self('This key is not compatible with this signer');
}
}

View File

@@ -0,0 +1,117 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT\Signer;
use Exception;
use InvalidArgumentException;
use Lcobucci\JWT\Signer\Key\FileCouldNotBeRead;
use SplFileObject;
use function strpos;
use function substr;
/**
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 3.0.4
*/
class Key
{
/**
* @var string
*/
protected $content;
/**
* @var string
*/
private $passphrase;
/**
* @param string $content
* @param string $passphrase
*/
public function __construct($content, $passphrase = '')
{
$this->setContent($content);
$this->passphrase = $passphrase;
}
/**
* @param string $content
*
* @throws InvalidArgumentException
*/
private function setContent($content)
{
if (strpos($content, 'file://') === 0) {
$content = $this->readFile($content);
}
$this->content = $content;
}
/**
* @param string $content
*
* @return string
*
* @throws InvalidArgumentException
*/
private function readFile($content)
{
$path = substr($content, 7);
try {
$file = new SplFileObject($path);
} catch (Exception $exception) {
throw FileCouldNotBeRead::onPath($path, $exception);
}
$content = '';
while (! $file->eof()) {
$content .= $file->fgets();
}
return $content;
}
/** @return string */
public function contents()
{
return $this->content;
}
/** @return string */
public function passphrase()
{
return $this->passphrase;
}
/**
* @deprecated This method is no longer part of the public interface
* @see Key::contents()
*
* @return string
*/
public function getContent()
{
return $this->content;
}
/**
* @deprecated This method is no longer part of the public interface
* @see Key::passphrase()
*
* @return string
*/
public function getPassphrase()
{
return $this->passphrase;
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Exception;
use InvalidArgumentException;
if (PHP_MAJOR_VERSION === 7) {
final class FileCouldNotBeRead extends InvalidArgumentException implements Exception
{
/** @return self */
public static function onPath(string $path, \Throwable $cause = null)
{
return new self(
'The path "' . $path . '" does not contain a valid key file',
0,
$cause
);
}
}
} else {
final class FileCouldNotBeRead extends InvalidArgumentException implements Exception
{
/**
* @param string $path
* @param \Exception|null $cause
*
* @return self
*/
public static function onPath($path, \Exception $cause = null)
{
return new self(
'The path "' . $path . '" does not contain a valid key file',
0,
$cause
);
}
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Encoding\CannotDecodeContent;
use Lcobucci\JWT\Signer\Key;
use function base64_decode;
final class InMemory extends Key
{
/**
* @param string $contents
* @param string $passphrase
*
* @return self
*/
public static function plainText($contents, $passphrase = '')
{
return new self($contents, $passphrase);
}
/**
* @param string $contents
* @param string $passphrase
*
* @return self
*/
public static function base64Encoded($contents, $passphrase = '')
{
$decoded = base64_decode($contents, true);
if ($decoded === false) {
throw CannotDecodeContent::invalidBase64String();
}
return new self($decoded, $passphrase);
}
/**
* @param string $path
* @param string $passphrase
*
* @return InMemory
*
* @throws FileCouldNotBeRead
*/
public static function file($path, $passphrase = '')
{
return new self('file://' . $path, $passphrase);
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Signer\Key;
use function file_exists;
use function strpos;
use function substr;
final class LocalFileReference extends Key
{
const PATH_PREFIX = 'file://';
/**
* @param string $path
* @param string $passphrase
*
* @return self
*
* @throws FileCouldNotBeRead
*/
public static function file($path, $passphrase = '')
{
if (strpos($path, self::PATH_PREFIX) === 0) {
$path = substr($path, 7);
}
if (! file_exists($path)) {
throw FileCouldNotBeRead::onPath($path);
}
$key = new self('', $passphrase);
$key->content = self::PATH_PREFIX . $path;
return $key;
}
}

View File

@@ -0,0 +1,44 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT\Signer;
/**
* A utilitarian class that encapsulates the retrieval of public and private keys
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 2.1.0
*
* @deprecated Since we've removed OpenSSL from ECDSA there's no reason to use this class
*/
class Keychain
{
/**
* Returns a private key from file path or content
*
* @param string $key
* @param string $passphrase
*
* @return Key
*/
public function getPrivateKey($key, $passphrase = null)
{
return new Key($key, $passphrase);
}
/**
* Returns a public key from file path or content
*
* @param string $certificate
*
* @return Key
*/
public function getPublicKey($certificate)
{
return new Key($certificate);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Lcobucci\JWT\Signer;
final class None extends BaseSigner
{
public function getAlgorithmId()
{
return 'none';
}
public function createHash($payload, Key $key)
{
return '';
}
public function doVerify($expected, $payload, Key $key)
{
return $expected === '';
}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace Lcobucci\JWT\Signer;
use InvalidArgumentException;
use function is_resource;
use function openssl_error_string;
use function openssl_free_key;
use function openssl_pkey_get_details;
use function openssl_pkey_get_private;
use function openssl_pkey_get_public;
use function openssl_sign;
use function openssl_verify;
abstract class OpenSSL extends BaseSigner
{
public function createHash($payload, Key $key)
{
$privateKey = $this->getPrivateKey($key->getContent(), $key->getPassphrase());
try {
$signature = '';
if (! openssl_sign($payload, $signature, $privateKey, $this->getAlgorithm())) {
throw CannotSignPayload::errorHappened(openssl_error_string());
}
return $signature;
} finally {
openssl_free_key($privateKey);
}
}
/**
* @param string $pem
* @param string $passphrase
*
* @return resource
*/
private function getPrivateKey($pem, $passphrase)
{
$privateKey = openssl_pkey_get_private($pem, $passphrase);
$this->validateKey($privateKey);
return $privateKey;
}
/**
* @param $expected
* @param $payload
* @param $key
* @return bool
*/
public function doVerify($expected, $payload, Key $key)
{
$publicKey = $this->getPublicKey($key->getContent());
$result = openssl_verify($payload, $expected, $publicKey, $this->getAlgorithm());
openssl_free_key($publicKey);
return $result === 1;
}
/**
* @param string $pem
*
* @return resource
*/
private function getPublicKey($pem)
{
$publicKey = openssl_pkey_get_public($pem);
$this->validateKey($publicKey);
return $publicKey;
}
/**
* Raises an exception when the key type is not the expected type
*
* @param resource|bool $key
*
* @throws InvalidArgumentException
*/
private function validateKey($key)
{
if (! is_resource($key)) {
throw InvalidKeyProvided::cannotBeParsed(openssl_error_string());
}
$details = openssl_pkey_get_details($key);
if (! isset($details['key']) || $details['type'] !== $this->getKeyType()) {
throw InvalidKeyProvided::incompatibleKey();
}
}
/**
* Returns the type of key to be used to create/verify the signature (using OpenSSL constants)
*
* @internal
*/
abstract public function getKeyType();
/**
* Returns which algorithm to be used to create/verify the signature (using OpenSSL constants)
*
* @internal
*/
abstract public function getAlgorithm();
}

View File

@@ -0,0 +1,24 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT\Signer;
use const OPENSSL_KEYTYPE_RSA;
/**
* Base class for RSASSA-PKCS1 signers
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 2.1.0
*/
abstract class Rsa extends OpenSSL
{
final public function getKeyType()
{
return OPENSSL_KEYTYPE_RSA;
}
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT\Signer\Rsa;
use Lcobucci\JWT\Signer\Rsa;
/**
* Signer for RSA SHA-256
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 2.1.0
*/
class Sha256 extends Rsa
{
/**
* {@inheritdoc}
*/
public function getAlgorithmId()
{
return 'RS256';
}
/**
* {@inheritdoc}
*/
public function getAlgorithm()
{
return OPENSSL_ALGO_SHA256;
}
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT\Signer\Rsa;
use Lcobucci\JWT\Signer\Rsa;
/**
* Signer for RSA SHA-384
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 2.1.0
*/
class Sha384 extends Rsa
{
/**
* {@inheritdoc}
*/
public function getAlgorithmId()
{
return 'RS384';
}
/**
* {@inheritdoc}
*/
public function getAlgorithm()
{
return OPENSSL_ALGO_SHA384;
}
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT\Signer\Rsa;
use Lcobucci\JWT\Signer\Rsa;
/**
* Signer for RSA SHA-512
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 2.1.0
*/
class Sha512 extends Rsa
{
/**
* {@inheritdoc}
*/
public function getAlgorithmId()
{
return 'RS512';
}
/**
* {@inheritdoc}
*/
public function getAlgorithm()
{
return OPENSSL_ALGO_SHA512;
}
}

View File

@@ -0,0 +1,430 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT;
use DateTimeImmutable;
use DateTimeInterface;
use Generator;
use Lcobucci\JWT\Claim\Factory;
use Lcobucci\JWT\Claim\Validatable;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Token\DataSet;
use Lcobucci\JWT\Token\RegisteredClaims;
use OutOfBoundsException;
use function current;
use function func_num_args;
use function in_array;
use function is_array;
use function sprintf;
use function trigger_error;
use const E_USER_DEPRECATED;
/**
* Basic structure of the JWT
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 0.1.0
*/
class Token
{
/**
* The token headers
*
* @var DataSet
*/
private $headers;
/**
* The token claim set
*
* @var DataSet
*/
private $claims;
/**
* The token signature
*
* @var Signature
*/
private $signature;
/**
* @internal This serves just as compatibility layer
*
* @var Factory
*/
private $claimFactory;
/**
* Initializes the object
*
* @param array|DataSet $headers
* @param array|DataSet $claims
* @param Signature|null $signature
* @param array $payload
* @param Factory|null $claimFactory
*/
public function __construct(
$headers = ['alg' => 'none'],
$claims = [],
Signature $signature = null,
array $payload = ['', ''],
Factory $claimFactory = null
) {
$this->headers = $this->convertToDataSet($headers, $payload[0]);
$this->claims = $this->convertToDataSet($claims, $payload[1]);
$this->signature = $signature ?: Signature::fromEmptyData();
$this->claimFactory = $claimFactory ?: new Factory();
}
/**
* @param array|DataSet $data
* @param string $payload
*/
private function convertToDataSet($data, $payload)
{
if ($data instanceof DataSet) {
return $data;
}
return new DataSet($data, $payload);
}
/** @return DataSet */
public function headers()
{
return $this->headers;
}
/**
* Returns the token headers
*
* @deprecated This method has been removed from the interface in v4.0
* @see Token::headers()
*
* @return array
*/
public function getHeaders()
{
$items = [];
foreach ($this->headers->all() as $name => $value) {
if (! in_array($name, RegisteredClaims::ALL, true) || ! $this->claims->has($name)) {
$items[$name] = $value;
continue;
}
$items[$name] = $this->claimFactory->create($name, $value);
}
return $items;
}
/**
* Returns if the header is configured
*
* @deprecated This method has been removed from the interface in v4.0
* @see Token::headers()
* @see DataSet::has()
*
* @param string $name
*
* @return boolean
*/
public function hasHeader($name)
{
return $this->headers->has($name);
}
/**
* Returns the value of a token header
*
* @deprecated This method has been removed from the interface in v4.0
* @see Token::headers()
* @see DataSet::has()
*
* @param string $name
* @param mixed $default
*
* @return mixed
*
* @throws OutOfBoundsException
*/
public function getHeader($name, $default = null)
{
if (func_num_args() === 1 && ! $this->headers->has($name)) {
throw new OutOfBoundsException(sprintf('Requested header "%s" is not configured', $name));
}
return $this->headers->get($name, $default);
}
/** @return DataSet */
public function claims()
{
return $this->claims;
}
/**
* Returns the token claim set
*
* @deprecated This method has been removed from the interface in v4.0
* @see Token::claims()
*
* @return array
*/
public function getClaims()
{
$items = [];
foreach ($this->claims->all() as $name => $value) {
$items[$name] = $this->claimFactory->create($name, $value);
}
return $items;
}
/**
* Returns if the claim is configured
*
* @deprecated This method has been removed from the interface in v4.0
* @see Token::claims()
* @see DataSet::has()
*
* @param string $name
*
* @return boolean
*/
public function hasClaim($name)
{
return $this->claims->has($name);
}
/**
* Returns the value of a token claim
*
* @deprecated This method has been removed from the interface in v4.0
* @see Token::claims()
* @see DataSet::get()
*
* @param string $name
* @param mixed $default
*
* @return mixed
*
* @throws OutOfBoundsException
*/
public function getClaim($name, $default = null)
{
if (func_num_args() === 1 && ! $this->claims->has($name)) {
throw new OutOfBoundsException(sprintf('Requested header "%s" is not configured', $name));
}
$value = $this->claims->get($name, $default);
if ($value instanceof DateTimeImmutable && in_array($name, RegisteredClaims::DATE_CLAIMS, true)) {
return $value->getTimestamp();
}
if ($name === RegisteredClaims::AUDIENCE && is_array($value)) {
if (count($value) > 1) {
trigger_error('You will only get the first array entry as a string. Use Token::claims()->get() instead.', E_USER_DEPRECATED);
}
return current($value);
}
return $value;
}
/**
* Verify if the key matches with the one that created the signature
*
* @deprecated This method has been removed from the interface in v4.0
* @see \Lcobucci\JWT\Validation\Validator
*
* @param Signer $signer
* @param Key|string $key
*
* @return boolean
*/
public function verify(Signer $signer, $key)
{
if ($this->headers->get('alg') !== $signer->getAlgorithmId()) {
return false;
}
return $this->signature->verify($signer, $this->getPayload(), $key);
}
/**
* Validates if the token is valid
*
* @deprecated This method has been removed from the interface in v4.0
* @see \Lcobucci\JWT\Validation\Validator
*
* @param ValidationData $data
*
* @return boolean
*/
public function validate(ValidationData $data)
{
foreach ($this->getValidatableClaims() as $claim) {
if (!$claim->validate($data)) {
return false;
}
}
return true;
}
/**
* Determine if the token is expired.
*
* @param DateTimeInterface|null $now Defaults to the current time.
*
* @return bool
*/
public function isExpired(DateTimeInterface $now = null)
{
if (! $this->claims->has('exp')) {
return false;
}
if ($now === null) {
trigger_error('Not providing the current time is deprecated. Please pass an instance of DateTimeInterface.', E_USER_DEPRECATED);
}
$now = $now ?: new DateTimeImmutable();
return $now >= $this->claims->get(RegisteredClaims::EXPIRATION_TIME);
}
/**
* @param string $audience
*
* @return bool
*/
public function isPermittedFor($audience)
{
return in_array($audience, $this->claims->get(RegisteredClaims::AUDIENCE, []), true);
}
/**
* @param string $id
*
* @return bool
*/
public function isIdentifiedBy($id)
{
return $this->claims->get(RegisteredClaims::ID) === $id;
}
/**
* @param string $subject
*
* @return bool
*/
public function isRelatedTo($subject)
{
return $this->claims->get(RegisteredClaims::SUBJECT) === $subject;
}
/**
* @param list<string> $issuers
*
* @return bool
*/
public function hasBeenIssuedBy(...$issuers)
{
return in_array($this->claims->get(RegisteredClaims::ISSUER), $issuers, true);
}
/**
* @param DateTimeInterface $now
*
* @return bool
*/
public function hasBeenIssuedBefore(DateTimeInterface $now)
{
return $now >= $this->claims->get(RegisteredClaims::ISSUED_AT);
}
/**
* @param DateTimeInterface $now
*
* @return bool
*/
public function isMinimumTimeBefore(DateTimeInterface $now)
{
return $now >= $this->claims->get(RegisteredClaims::NOT_BEFORE);
}
/**
* Yields the validatable claims
*
* @return Generator
*/
private function getValidatableClaims()
{
foreach ($this->getClaims() as $claim) {
if ($claim instanceof Validatable) {
yield $claim;
}
}
}
/**
* Returns the token payload
*
* @deprecated This method has been removed from the interface in v4.0
* @see Token::payload()
*
* @return string
*/
public function getPayload()
{
return $this->payload();
}
/**
* Returns the token payload
*
* @return string
*/
public function payload()
{
return $this->headers->toString() . '.' . $this->claims->toString();
}
/** @return Signature */
public function signature()
{
return $this->signature;
}
/**
* Returns an encoded representation of the token
*
* @deprecated This method has been removed from the interface in v4.0
* @see Token::toString()
*
* @return string
*/
public function __toString()
{
return $this->toString();
}
/** @return string */
public function toString()
{
return $this->headers->toString() . '.'
. $this->claims->toString() . '.'
. $this->signature->toString();
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace Lcobucci\JWT\Token;
use function array_key_exists;
final class DataSet
{
/** @var array<string, mixed> */
private $data;
/** @var string */
private $encoded;
/**
* @param array<string, mixed> $data
* @param string $encoded
*/
public function __construct(array $data, $encoded)
{
$this->data = $data;
$this->encoded = $encoded;
}
/**
* @param string $name
* @param mixed|null $default
*
* @return mixed|null
*/
public function get($name, $default = null)
{
return $this->has($name) ? $this->data[$name] : $default;
}
/**
* @param string $name
*
* @return bool
*/
public function has($name)
{
return array_key_exists($name, $this->data);
}
/** @return array<string, mixed> */
public function all()
{
return $this->data;
}
/** @return string */
public function toString()
{
return $this->encoded;
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Lcobucci\JWT\Token;
use InvalidArgumentException;
use Lcobucci\JWT\Exception;
final class InvalidTokenStructure extends InvalidArgumentException implements Exception
{
/** @return self */
public static function missingOrNotEnoughSeparators()
{
return new self('The JWT string must have two dots');
}
/**
* @param string $part
*
* @return self
*/
public static function arrayExpected($part)
{
return new self($part . ' must be an array');
}
/**
* @param string $value
*
* @return self
*/
public static function dateIsNotParseable($value)
{
return new self('Value is not in the allowed date format: ' . $value);
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace Lcobucci\JWT\Token;
use Lcobucci\JWT\Token;
use function class_alias;
class_exists(Plain::class, false) || class_alias(Token::class, Plain::class);

View File

@@ -0,0 +1,24 @@
<?php
namespace Lcobucci\JWT\Token;
use InvalidArgumentException;
use Lcobucci\JWT\Exception;
use function sprintf;
final class RegisteredClaimGiven extends InvalidArgumentException implements Exception
{
const DEFAULT_MESSAGE = 'Builder#withClaim() is meant to be used for non-registered claims, '
. 'check the documentation on how to set claim "%s"';
/**
* @param string $name
*
* @return self
*/
public static function forClaim($name)
{
return new self(sprintf(self::DEFAULT_MESSAGE, $name));
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace Lcobucci\JWT\Token;
/**
* Defines the list of claims that are registered in the IANA "JSON Web Token Claims" registry
*
* @see https://tools.ietf.org/html/rfc7519#section-4.1
*/
interface RegisteredClaims
{
const ALL = [
self::AUDIENCE,
self::EXPIRATION_TIME,
self::ID,
self::ISSUED_AT,
self::ISSUER,
self::NOT_BEFORE,
self::SUBJECT,
];
const DATE_CLAIMS = [
self::ISSUED_AT,
self::NOT_BEFORE,
self::EXPIRATION_TIME,
];
/**
* Identifies the recipients that the JWT is intended for
*
* @see https://tools.ietf.org/html/rfc7519#section-4.1.3
*/
const AUDIENCE = 'aud';
/**
* Identifies the expiration time on or after which the JWT MUST NOT be accepted for processing
*
* @see https://tools.ietf.org/html/rfc7519#section-4.1.4
*/
const EXPIRATION_TIME = 'exp';
/**
* Provides a unique identifier for the JWT
*
* @see https://tools.ietf.org/html/rfc7519#section-4.1.7
*/
const ID = 'jti';
/**
* Identifies the time at which the JWT was issued
*
* @see https://tools.ietf.org/html/rfc7519#section-4.1.6
*/
const ISSUED_AT = 'iat';
/**
* Identifies the principal that issued the JWT
*
* @see https://tools.ietf.org/html/rfc7519#section-4.1.1
*/
const ISSUER = 'iss';
/**
* Identifies the time before which the JWT MUST NOT be accepted for processing
*
* https://tools.ietf.org/html/rfc7519#section-4.1.5
*/
const NOT_BEFORE = 'nbf';
/**
* Identifies the principal that is the subject of the JWT.
*
* https://tools.ietf.org/html/rfc7519#section-4.1.2
*/
const SUBJECT = 'sub';
}

View File

@@ -0,0 +1,8 @@
<?php
namespace Lcobucci\JWT\Token;
use Lcobucci\JWT\Signature as SignatureImpl;
use function class_alias;
class_exists(Signature::class, false) || class_alias(SignatureImpl::class, Signature::class);

View File

@@ -0,0 +1,15 @@
<?php
namespace Lcobucci\JWT\Token;
use InvalidArgumentException;
use Lcobucci\JWT\Exception;
final class UnsupportedHeaderFound extends InvalidArgumentException implements Exception
{
/** @return self */
public static function encryption()
{
return new self('Encryption is not supported yet');
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace Lcobucci\JWT\Validation;
use Lcobucci\JWT\Token;
interface Constraint
{
/** @throws ConstraintViolation */
public function assert(Token $token);
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Lcobucci\JWT\Validation\Constraint;
use Lcobucci\JWT\Token;
use Lcobucci\JWT\Validation\Constraint;
use Lcobucci\JWT\Validation\ConstraintViolation;
final class IdentifiedBy implements Constraint
{
/** @var string */
private $id;
/** @param string $id */
public function __construct($id)
{
$this->id = $id;
}
public function assert(Token $token)
{
if (! $token->isIdentifiedBy($this->id)) {
throw new ConstraintViolation(
'The token is not identified with the expected ID'
);
}
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Lcobucci\JWT\Validation\Constraint;
use Lcobucci\JWT\Token;
use Lcobucci\JWT\Validation\Constraint;
use Lcobucci\JWT\Validation\ConstraintViolation;
final class IssuedBy implements Constraint
{
/** @var string[] */
private $issuers;
/** @param list<string> $issuers */
public function __construct(...$issuers)
{
$this->issuers = $issuers;
}
public function assert(Token $token)
{
if (! $token->hasBeenIssuedBy(...$this->issuers)) {
throw new ConstraintViolation(
'The token was not issued by the given issuers'
);
}
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace Lcobucci\JWT\Validation\Constraint;
use InvalidArgumentException;
use Lcobucci\JWT\Exception;
final class LeewayCannotBeNegative extends InvalidArgumentException implements Exception
{
/** @return self */
public static function create()
{
return new self('Leeway cannot be negative');
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Lcobucci\JWT\Validation\Constraint;
use Lcobucci\JWT\Token;
use Lcobucci\JWT\Validation\Constraint;
use Lcobucci\JWT\Validation\ConstraintViolation;
final class PermittedFor implements Constraint
{
/** @var string */
private $audience;
public function __construct($audience)
{
$this->audience = $audience;
}
public function assert(Token $token)
{
if (! $token->isPermittedFor($this->audience)) {
throw new ConstraintViolation(
'The token is not allowed to be used by this audience'
);
}
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Lcobucci\JWT\Validation\Constraint;
use Lcobucci\JWT\Token;
use Lcobucci\JWT\Validation\Constraint;
use Lcobucci\JWT\Validation\ConstraintViolation;
final class RelatedTo implements Constraint
{
/** @var string */
private $subject;
public function __construct($subject)
{
$this->subject = $subject;
}
public function assert(Token $token)
{
if (! $token->isRelatedTo($this->subject)) {
throw new ConstraintViolation(
'The token is not related to the expected subject'
);
}
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Lcobucci\JWT\Validation\Constraint;
use Lcobucci\JWT\Signer;
use Lcobucci\JWT\Token;
use Lcobucci\JWT\Validation\Constraint;
use Lcobucci\JWT\Validation\ConstraintViolation;
final class SignedWith implements Constraint
{
/** @var Signer */
private $signer;
/** @var Signer\Key */
private $key;
public function __construct(Signer $signer, Signer\Key $key)
{
$this->signer = $signer;
$this->key = $key;
}
public function assert(Token $token)
{
if ($token->headers()->get('alg') !== $this->signer->getAlgorithmId()) {
throw new ConstraintViolation('Token signer mismatch');
}
if (! $this->signer->verify((string) $token->signature(), $token->getPayload(), $this->key)) {
throw new ConstraintViolation('Token signature mismatch');
}
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace Lcobucci\JWT\Validation\Constraint;
use DateInterval;
use DateTimeInterface;
use Lcobucci\Clock\Clock;
use Lcobucci\JWT\Token;
use Lcobucci\JWT\Validation\Constraint;
use Lcobucci\JWT\Validation\ConstraintViolation;
final class ValidAt implements Constraint
{
/** @var Clock */
private $clock;
/** @var DateInterval */
private $leeway;
public function __construct(Clock $clock, DateInterval $leeway = null)
{
$this->clock = $clock;
$this->leeway = $this->guardLeeway($leeway);
}
/** @return DateInterval */
private function guardLeeway(DateInterval $leeway = null)
{
if ($leeway === null) {
return new DateInterval('PT0S');
}
if ($leeway->invert === 1) {
throw LeewayCannotBeNegative::create();
}
return $leeway;
}
public function assert(Token $token)
{
$now = $this->clock->now();
$this->assertIssueTime($token, $now->add($this->leeway));
$this->assertMinimumTime($token, $now->add($this->leeway));
$this->assertExpiration($token, $now->sub($this->leeway));
}
/** @throws ConstraintViolation */
private function assertExpiration(Token $token, DateTimeInterface $now)
{
if ($token->isExpired($now)) {
throw new ConstraintViolation('The token is expired');
}
}
/** @throws ConstraintViolation */
private function assertMinimumTime(Token $token, DateTimeInterface $now)
{
if (! $token->isMinimumTimeBefore($now)) {
throw new ConstraintViolation('The token cannot be used yet');
}
}
/** @throws ConstraintViolation */
private function assertIssueTime(Token $token, DateTimeInterface $now)
{
if (! $token->hasBeenIssuedBefore($now)) {
throw new ConstraintViolation('The token was issued in the future');
}
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Lcobucci\JWT\Validation;
use Lcobucci\JWT\Exception;
use RuntimeException;
final class ConstraintViolation extends RuntimeException implements Exception
{
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Lcobucci\JWT\Validation;
use Lcobucci\JWT\Exception;
use RuntimeException;
final class NoConstraintsGiven extends RuntimeException implements Exception
{
}

View File

@@ -0,0 +1,53 @@
<?php
namespace Lcobucci\JWT\Validation;
use Lcobucci\JWT\Exception;
use RuntimeException;
use function array_map;
use function implode;
final class RequiredConstraintsViolated extends RuntimeException implements Exception
{
/** @var ConstraintViolation[] */
private $violations = [];
/**
* @param ConstraintViolation ...$violations
* @return self
*/
public static function fromViolations(ConstraintViolation ...$violations)
{
$exception = new self(self::buildMessage($violations));
$exception->violations = $violations;
return $exception;
}
/**
* @param ConstraintViolation[] $violations
*
* @return string
*/
private static function buildMessage(array $violations)
{
$violations = array_map(
static function (ConstraintViolation $violation) {
return '- ' . $violation->getMessage();
},
$violations
);
$message = "The token violates some mandatory constraints, details:\n";
$message .= implode("\n", $violations);
return $message;
}
/** @return ConstraintViolation[] */
public function violations()
{
return $this->violations;
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Lcobucci\JWT\Validation;
use Lcobucci\JWT\Token;
final class Validator implements \Lcobucci\JWT\Validator
{
public function assert(Token $token, Constraint ...$constraints)
{
if ($constraints === []) {
throw new NoConstraintsGiven('No constraint given.');
}
$violations = [];
foreach ($constraints as $constraint) {
$this->checkConstraint($constraint, $token, $violations);
}
if ($violations) {
throw RequiredConstraintsViolated::fromViolations(...$violations);
}
}
/** @param ConstraintViolation[] $violations */
private function checkConstraint(
Constraint $constraint,
Token $token,
array &$violations
) {
try {
$constraint->assert($token);
} catch (ConstraintViolation $e) {
$violations[] = $e;
}
}
public function validate(Token $token, Constraint ...$constraints)
{
if ($constraints === []) {
throw new NoConstraintsGiven('No constraint given.');
}
try {
foreach ($constraints as $constraint) {
$constraint->assert($token);
}
return true;
} catch (ConstraintViolation $e) {
return false;
}
}
}

View File

@@ -0,0 +1,132 @@
<?php
/**
* This file is part of Lcobucci\JWT, a simple library to handle JWT and JWS
*
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
*/
namespace Lcobucci\JWT;
/**
* Class that wraps validation values
*
* @deprecated This component has been removed from the interface in v4.0
* @see \Lcobucci\JWT\Validation\Validator
*
* @author Luís Otávio Cobucci Oblonczyk <lcobucci@gmail.com>
* @since 2.0.0
*/
class ValidationData
{
/**
* The list of things to be validated
*
* @var array
*/
private $items;
/**
* The leeway (in seconds) to use when validating time claims
* @var int
*/
private $leeway;
/**
* Initializes the object
*
* @param int $currentTime
* @param int $leeway
*/
public function __construct($currentTime = null, $leeway = 0)
{
$currentTime = $currentTime ?: time();
$this->leeway = (int) $leeway;
$this->items = [
'jti' => null,
'iss' => null,
'aud' => null,
'sub' => null
];
$this->setCurrentTime($currentTime);
}
/**
* Configures the id
*
* @param string $id
*/
public function setId($id)
{
$this->items['jti'] = (string) $id;
}
/**
* Configures the issuer
*
* @param string $issuer
*/
public function setIssuer($issuer)
{
$this->items['iss'] = (string) $issuer;
}
/**
* Configures the audience
*
* @param string $audience
*/
public function setAudience($audience)
{
$this->items['aud'] = (string) $audience;
}
/**
* Configures the subject
*
* @param string $subject
*/
public function setSubject($subject)
{
$this->items['sub'] = (string) $subject;
}
/**
* Configures the time that "iat", "nbf" and "exp" should be based on
*
* @param int $currentTime
*/
public function setCurrentTime($currentTime)
{
$currentTime = (int) $currentTime;
$this->items['iat'] = $currentTime + $this->leeway;
$this->items['nbf'] = $currentTime + $this->leeway;
$this->items['exp'] = $currentTime - $this->leeway;
}
/**
* Returns the requested item
*
* @param string $name
*
* @return mixed
*/
public function get($name)
{
return isset($this->items[$name]) ? $this->items[$name] : null;
}
/**
* Returns if the item is present
*
* @param string $name
*
* @return boolean
*/
public function has($name)
{
return !empty($this->items[$name]);
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Lcobucci\JWT;
use Lcobucci\JWT\Validation\Constraint;
use Lcobucci\JWT\Validation\NoConstraintsGiven;
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
interface Validator
{
/**
* @throws RequiredConstraintsViolated
* @throws NoConstraintsGiven
*/
public function assert(Token $token, Constraint ...$constraints);
/**
* @return bool
*
* @throws NoConstraintsGiven
*/
public function validate(Token $token, Constraint ...$constraints);
}