Compare commits

...

11 Commits

Author SHA1 Message Date
Chris f83998e6c7 Additional checks for validity and in validator
* Properly check nbf and exp claims in token to determine simple
  validity.
* Properly check nbf and exp claims in validator and throw exceptions
  if expired/not yet valid.
2024-03-11 23:34:19 +01:00
Chris 369514589f Added woodpecker config
ci/woodpecker/push/woodpecker Pipeline failed Details
2023-04-21 01:46:52 +02:00
Chris b0566e3148 Fixed case in test filenames 2023-04-10 00:55:15 +02:00
Chris c5dd773026 Added docblocks 2023-04-10 00:53:51 +02:00
Chris 5c422226fd Fixed test @covers annotations 2023-04-10 00:43:42 +02:00
Chris e30ded1e66 Added changelog 2023-04-09 16:04:34 +02:00
Chris 822b796d40 phpstan fixes 2023-04-09 14:12:48 +02:00
Chris b9c690cb6e Fixed readme 2023-04-09 02:46:48 +02:00
Chris fabf160346 Useless merge 2023-04-09 02:44:23 +02:00
Chris 953e831d84 Fixed capitalization, tests 2023-04-09 02:40:21 +02:00
Chris 5a4a2845e4 Update 'README.md'
Fixed code blocks
2022-12-29 01:18:13 +00:00
32 changed files with 552 additions and 378 deletions

7
.woodpecker.yml Normal file
View File

@ -0,0 +1,7 @@
pipeline:
phpunit:
image: walkero/phpunit-alpine:php8.1-phpunit9
commands:
- composer install
- vendor/bin/phpunit --testdox --no-progress
- vendor/bin/phpstan --no-progress

14
CHANGELOG.md Normal file
View File

@ -0,0 +1,14 @@
# SimpleJWT ChangeLog
## 0.2.1
- Mostly code cleanup
## 0.2.0
- Class- and filenames have had their case changed (`Jwt`→`JWT`)
- Added phpstan for static analysis
## 0.1.0
- Initial release

View File

@ -1,4 +1,4 @@
# SimpleJwt
# SimpleJWT
This is a simple library for generating (signing) and verifying JWT tokens. It
is by no means an advanced library. If you just need to sign and refresh tokens
@ -27,69 +27,77 @@ Install using composer:
## Usage
You need a key for both generating and parsing tokens. Create a `JwtDerivedKey`
or a `JwtPlaintextKey` and pass it to the `JwtToken` constructor:
You need a key for both generating and parsing tokens. Create a `JWTDerivedKey`
or a `JWTPlaintextKey` and pass it to the `JWTToken` constructor:
use NoccyLabs\SimpleJwt\Key\{JwtDerivedKey,JwtPlaintextKey}
```php
use NoccyLabs\SimpleJWT\Key\{JWTDerivedKey,JWTPlaintextKey}
// Derive a key using secret and salt...
$key = new JwtDerivedKey("secret", "salt");
// ...or use a prepared plaintext key
$key = new JwtPlaintextKey("This Should Be Binary Data..");
// Derive a key using secret and salt...
$key = new JWTDerivedKey("secret", "salt");
// ...or use a prepared plaintext key
$key = new JWTPlaintextKey("This Should Be Binary Data..");
```
`JWTDerivedKey` uses hash_pbkdf2.
### Generating tokens
```php
use NoccyLabs\SimpleJWT\JWTToken;
use NoccyLabs\SimpleJwt\JwtToken;
$tok = new JwtToken($key);
$tok->setExpiry("1h");
$tok->claims->add("some/claim/MaxItems", 8);
$str = $tok->getSignedToken();
$tok = new JWTToken($key);
$tok->setExpiry("1h");
$tok->claims->add("some/claim/MaxItems", 8);
$str = $tok->getSignedToken();
```
### Parsing tokens
Parsing is done by passing the raw token as the 2nd parameter
use NoccyLabs\SimpleJwt\JwtToken;
```php
use NoccyLabs\SimpleJWT\JWTToken;
$str = "...received token...";
$str = "...received token...";
$tok = new JwtToken($key, $str);
$tok = new JWTToken($key, $str);
if (!$tok->isValid()) {
// This check works, but using the validator might be better
}
if (!$tok->isValid()) {
// This check works, but using the validator might be better
}
// Using ->has() follwed by ->get() is one way
if ($tok->claims->has("some/claim/MaxItems")) {
// The claim exists, we can get the value (if any)
$val = $tok->claims->get("some/claim/MaxItems");
}
// Using ->has() follwed by ->get() is one way
if ($tok->claims->has("some/claim/MaxItems")) {
// The claim exists, we can get the value (if any)
$val = $tok->claims->get("some/claim/MaxItems");
}
// You can also use valueOf() to return a default value if needed
$val = $tok->claims->valueOf("some/claim/MaxItems", 64);
// You can also use valueOf() to return a default value if needed
$val = $tok->claims->valueOf("some/claim/MaxItems", 64);
```
### Validating tokens
use NoccyLabs\SimpleJwt\Validator\JwtValidator;
```php
use NoccyLabs\SimpleJWT\Validator\JWTValidator;
$validator = new JwtValidator();
// Require that some claim exists
$validator
->requireIssuer("api.issuer.tld")
->requireAudience(["api.issuer.tld", "foo.issuer.tld"])
->addRequiredClaim("some/required/Claim");
$validator = new JWTValidator();
// Require that some claim exists
$validator
->requireIssuer("api.issuer.tld")
->requireAudience(["api.issuer.tld", "foo.issuer.tld"])
->addRequiredClaim("some/required/Claim");
try {
// Pass a JwtToken to validateToken()...
$valid = $validator->validateToken($tok);
// ...or pass a JwtKeyInterface and the raw string to validate()
$valid = $validator->validate($key, $tokenstr);
}
catch (JwtValidatorException $e) {
// validation failed
}
try {
// Pass a JWTToken to validateToken()...
$valid = $validator->validateToken($tok);
// ...or pass a JWTKeyInterface and the raw string to validate()
$valid = $validator->validate($key, $tokenstr);
}
catch (JWTValidatorException $e) {
// validation failed
}
```

View File

@ -11,7 +11,15 @@
],
"autoload": {
"psr-4": {
"NoccyLabs\\SimpleJwt\\": "src/"
"NoccyLabs\\SimpleJWT\\": "src/"
}
},
"require": {
"php": "^7.4|^8.0",
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "^10.0",
"phpstan/phpstan": "^1.10"
}
}
}

12
phpstan.neon Normal file
View File

@ -0,0 +1,12 @@
parameters:
level: 5
excludePaths:
- doc
- vendor
- tests
# Paths to include in the analysis
paths:
- src

View File

@ -1,22 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.5/phpunit.xsd"
bootstrap="vendor/autoload.php"
executionOrder="depends,defects"
forceCoversAnnotation="true"
beStrictAboutCoversAnnotation="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutTodoAnnotatedTests="true"
verbose="true">
<testsuites>
<testsuite name="default">
<directory suffix="Test.php">tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src</directory>
</whitelist>
</filter>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.0/phpunit.xsd" bootstrap="vendor/autoload.php" executionOrder="depends,defects" beStrictAboutOutputDuringTests="true" cacheDirectory=".phpunit.cache" requireCoverageMetadata="true" beStrictAboutCoverageMetadata="true">
<coverage>
<include>
<directory suffix=".php">src</directory>
</include>
</coverage>
<testsuites>
<testsuite name="default">
<directory suffix="Test.php">tests</directory>
</testsuite>
</testsuites>
</phpunit>

View File

@ -1,6 +1,6 @@
<?php
namespace NoccyLabs\SimpleJwt\Collection;
namespace NoccyLabs\SimpleJWT\Collection;
use ArrayAccess;
use Countable;
@ -21,8 +21,8 @@ class PropertyBag
/**
* Add a property value, fails if the property exists
*
* @param string Property name
* @param mixed Value
* @param string $prop Property name
* @param mixed $value Value
* @throws PropertyException if the property already exists
*/
public function add(string $prop, $value)
@ -37,14 +37,19 @@ class PropertyBag
* Set a property value, create the property if it doesn't
* exist.
*
* @param string Property name
* @param mixed Value
* @param string $prop Property name
* @param mixed $value Value
*/
public function set(string $prop, $value)
{
$this->props[$prop] = $value;
}
/**
* Apply properties without removing anything.
*
* @param array $props The properties to apply
*/
public function setAll(array $props)
{
$this->props = array_merge(
@ -57,7 +62,7 @@ class PropertyBag
* Get the value of a property, fails if the property does not exist.
* Use the value() method to get with a default value
*
* @param string Property name
* @param string $prop Property name
* @return mixed
* @throws PropertyException if the property does not exist
*/
@ -92,8 +97,8 @@ class PropertyBag
/**
* Get the value of the property, or use the provided default value.
*
* @param string Property name
* @param mixed Default value
* @param string $prop Property name
* @param mixed|null $default Default value
* @return mixed
*/
public function valueOf(string $prop, $default=null)
@ -105,6 +110,8 @@ class PropertyBag
/**
* Remove a property
*
* @param string $prop Property name
*/
public function delete(string $prop)
{
@ -113,6 +120,8 @@ class PropertyBag
/**
* Check if a property is present
*
* @param string $prop Property name
*/
public function has(string $prop): bool
{
@ -121,6 +130,8 @@ class PropertyBag
/**
* Check if all the provided properties are present
*
* @param array $props Property names
*/
public function hasAll(array $props)
{

View File

@ -1,6 +1,6 @@
<?php
namespace NoccyLabs\SimpleJwt\Collection;
namespace NoccyLabs\SimpleJWT\Collection;
class PropertyException extends \RuntimeException

View File

@ -1,19 +1,19 @@
<?php
namespace NoccyLabs\SimpleJwt;
namespace NoccyLabs\SimpleJWT;
use NoccyLabs\SimpleJwt\Collection\PropertyBag;
use NoccyLabs\SimpleJwt\Key\KeyInterface;
use NoccyLabs\SimpleJWT\Collection\PropertyBag;
use NoccyLabs\SimpleJWT\Key\KeyInterface;
/**
*
*
*
*
* @property-read header PropertyBag
* @property-read claim PropertyBag
* @property-read PropertyBag $header
* @property-read PropertyBag $claims
*/
class JwtToken
class JWTToken
{
/** @var PropertyBag */
private $header;
@ -30,7 +30,8 @@ class JwtToken
* Constructor
*
*
* @param KeyInterface The key used to sign the token
* @param KeyInterface $key The key used to sign the token
* @param string|null $token Token data
*/
public function __construct(KeyInterface $key, ?string $token=null)
{
@ -54,13 +55,13 @@ class JwtToken
$this->generated = false;
[ $header, $payload, $signature ] = explode(".", trim($token), 3);
$hash = JwtUtil::encode(hash_hmac("sha256", $header.".".$payload, $this->key->getBinaryKey(), true));
$hash = JWTUtil::encode(hash_hmac("sha256", $header.".".$payload, $this->key->getBinaryKey(), true));
if ($signature == $hash) {
$this->valid = true;
}
$this->header = new PropertyBag(json_decode(JwtUtil::decode($header), true));
$this->claims = new PropertyBag(json_decode(JwtUtil::decode($payload), true));
$this->header = new PropertyBag(json_decode(JWTUtil::decode($header), true));
$this->claims = new PropertyBag(json_decode(JWTUtil::decode($payload), true));
if ($this->header->has('exp')) {
$exp = intval($this->header->get('exp'));
@ -69,13 +70,35 @@ class JwtToken
$this->valid = false;
}
}
if ($this->header->has('nbf')) {
$nbf = intval($this->header->get('nbf'));
if ($nbf >= time()) {
// Invalid if before
$this->valid = false;
}
}
}
/**
* Returns true if the expiry is not in the past.
*
* NOTE: This function will return true if the expiry header is missing, and
* it will not validate any claims. For actual verification of a token matching
* issuers, audience or other claims, see Validator\JWTValidator.
*
* @return bool True if the token expiry timestamp is missing or in the future
*/
public function isValid(): bool
{
return $this->valid;
}
/**
* Returns true if the token was generated as opposed to parsed.
*
* @return bool
*/
public function isGenerated(): bool
{
return $this->generated;
@ -89,16 +112,44 @@ class JwtToken
}
}
/**
* Add a claim to a token. Throws an exception if the claim already exists.
*
* @param string $name The name of the claim
* @param mixed $value Claim value
* @throws \NoccyLabs\SimpleJWT\Collection\PropertyException if the claim already exists.
*/
public function addClaim(string $name, $value)
{
$this->claims->add($name, $value);
}
/**
* Add a claim to a token. If the claim already exists it will be updated with
* the provided value.
*
* @param string $name The name of the claim
* @param mixed $value Claim value
*/
public function setClaim(string $name, $value)
{
$this->claims->set($name, $value);
}
/**
* Set the time of expiry for the token.
*
* The expiry can be supplied as:
* - \DateTime instance
* - Unixtime as an integer
* - A string represening the expiry time
* - A period followed by a letter (m,h,d,w)
* - null, to unset the expiry
*
* @param string|int|\DateTime $expiry
* @return void
* @throws \InvalidArgumentException if the argument can not be interpreted
*/
public function setExpiry($expiry)
{
if ($expiry instanceof \DateTime) {
@ -127,6 +178,8 @@ class JwtToken
case 'm':
$fact = 60;
break;
default:
throw new \InvalidArgumentException();
}
$this->header->set('exp', time() + (intval($match[1]) * $fact));
} else {
@ -136,9 +189,9 @@ class JwtToken
public function getSignedToken(): string
{
$header = JwtUtil::encode($this->header->getJson());
$payload = JwtUtil::encode($this->claims->getJson());
$hash = JwtUtil::encode(hash_hmac("sha256", $header.".".$payload, $this->key->getBinaryKey(), true));
$header = JWTUtil::encode($this->header->getJson());
$payload = JWTUtil::encode($this->claims->getJson());
$hash = JWTUtil::encode(hash_hmac("sha256", $header.".".$payload, $this->key->getBinaryKey(), true));
return $header.".".$payload.".".$hash;
}

View File

@ -1,9 +1,9 @@
<?php
namespace NoccyLabs\SimpleJwt;
namespace NoccyLabs\SimpleJWT;
class JwtUtil
class JWTUtil
{
public static function encode($data) {
return rtrim(str_replace(['+', '/'], ['-', '_'], base64_encode($data)), "=");

View File

@ -1,8 +1,8 @@
<?php
namespace NoccyLabs\SimpleJwt\Key;
namespace NoccyLabs\SimpleJWT\Key;
class JwtDerivedKey implements KeyInterface
class JWTDerivedKey implements KeyInterface
{
private $key;

View File

@ -1,8 +1,8 @@
<?php
namespace NoccyLabs\SimpleJwt\Key;
namespace NoccyLabs\SimpleJWT\Key;
class JwtPlaintextKey implements KeyInterface
class JWTPlaintextKey implements KeyInterface
{
private $key;

View File

@ -1,6 +1,6 @@
<?php
namespace NoccyLabs\SimpleJwt\Key;
namespace NoccyLabs\SimpleJWT\Key;
interface KeyInterface
{

View File

@ -0,0 +1,9 @@
<?php
namespace NoccyLabs\SimpleJWT\Validator;
class JWTClaimException extends JWTValidatorException
{
}

View File

@ -0,0 +1,8 @@
<?php
namespace NoccyLabs\SimpleJWT\Validator;
class JWTHeaderException extends JWTValidatorException
{
}

View File

@ -0,0 +1,8 @@
<?php
namespace NoccyLabs\SimpleJWT\Validator;
class JWTTokenException extends JWTValidatorException
{
}

View File

@ -1,11 +1,11 @@
<?php
namespace NoccyLabs\SimpleJwt\Validator;
namespace NoccyLabs\SimpleJWT\Validator;
use NoccyLabs\SimpleJwt\JwtToken;
use NoccyLabs\SimpleJwt\Key\KeyInterface;
use NoccyLabs\SimpleJWT\JWTToken;
use NoccyLabs\SimpleJWT\Key\KeyInterface;
class JwtValidator
class JWTValidator
{
private $requireHeaders = [];
@ -41,32 +41,44 @@ class JwtValidator
$this->requireAudience = (array)$audience;
}
public function validateToken(JwtToken $token)
public function validateToken(JWTToken $token)
{
if (!$token->isValid()) {
throw new JwtTokenException("The token is not valid");
throw new JWTTokenException("The token is not valid");
}
if ($token->claims->has("nbf")) {
$notBefore = intval($token->claims->get("nbf"));
if (time() < $notBefore)
throw new JWTTokenException("Token not yet valid");
}
if ($token->claims->has("exp")) {
$notAfter = intval($token->claims->get("exp"));
if (time() > $notAfter)
throw new JWTTokenException("Token no longer valid");
}
if (!$token->header->hasAll($this->requireHeaders)) {
throw new JwtHeaderException("The token is missing one or more required headers");
throw new JWTHeaderException("The token is missing one or more required headers");
}
if (!$token->claims->hasAll($this->requireClaims)) {
throw new JwtHeaderException("The token is missing one or more required claims");
throw new JWTHeaderException("The token is missing one or more required claims");
}
if ($this->requireIssuer) {
$hasIssuer = $token->header->has("iss");
if ((!$hasIssuer)
|| (!in_array($token->header->get("iss"), $this->requireIssuer)))
throw new JwtTokenException("Invalid issuer");
throw new JWTTokenException("Invalid issuer");
}
if ($this->requireAudience) {
$hasAudience = $token->header->has("aud");
if ((!$hasAudience)
|| (!in_array($token->header->get("aud"), $this->requireAudience)))
throw new JwtTokenException("Invalid audience");
throw new JWTTokenException("Invalid audience");
}
return true;
@ -74,7 +86,7 @@ class JwtValidator
public function validate(KeyInterface $key, string $raw)
{
$token = new JwtToken($key, $raw);
$token = new JWTToken($key, $raw);
if ($this->validateToken($token)) {
return $token;
}

View File

@ -0,0 +1,8 @@
<?php
namespace NoccyLabs\SimpleJWT\Validator;
class JWTValidatorException extends \RuntimeException
{
}

View File

@ -1,9 +0,0 @@
<?php
namespace NoccyLabs\SimpleJwt\Validator;
class JwtClaimException extends JwtValidatorException
{
}

View File

@ -1,8 +0,0 @@
<?php
namespace NoccyLabs\SimpleJwt\Validator;
class JwtHeaderException extends JwtValidatorException
{
}

View File

@ -1,8 +0,0 @@
<?php
namespace NoccyLabs\SimpleJwt\Validator;
class JwtTokenException extends JwtValidatorException
{
}

View File

@ -1,8 +0,0 @@
<?php
namespace NoccyLabs\SimpleJwt\Validator;
class JwtValidatorException extends \RuntimeException
{
}

50
tests/JWTTokenTest.php Normal file
View File

@ -0,0 +1,50 @@
<?php
namespace NoccyLabs\SimpleJWT;
use NoccyLabs\SimpleJWT\Key\JWTPlaintextKey;
class JWTTokenTest extends \PHPUnit\Framework\TestCase
{
/**
* @covers NoccyLabs\SimpleJWT\JWTToken
* @covers NoccyLabs\SimpleJWT\Key\JWTPlaintextKey
* @covers NoccyLabs\SimpleJWT\Collection\PropertyBag
* @covers NoccyLabs\SimpleJWT\JWTUtil
*/
public function testGeneratingTokens()
{
$key = new JWTPlaintextKey("test");
$tok = new JWTToken($key);
$tok->addClaim("foo", true);
$token = $tok->getSignedToken();
$this->assertNotNull($token);
$this->assertTrue($tok->isGenerated());
}
/**
* @covers NoccyLabs\SimpleJWT\JWTToken
* @covers NoccyLabs\SimpleJWT\Key\JWTPlaintextKey
* @covers NoccyLabs\SimpleJWT\Collection\PropertyBag
* @covers NoccyLabs\SimpleJWT\JWTUtil
*/
public function testParsingTokens()
{
$key = new JWTPlaintextKey("test");
$tok = new JWTToken($key);
$tok->addClaim("foo", true);
$token = $tok->getSignedToken();
$parsed = new JWTToken($key, $token);
$this->assertTrue($parsed->isValid());
$this->assertFalse($parsed->isGenerated());
}
}

22
tests/JWTUtilTest.php Normal file
View File

@ -0,0 +1,22 @@
<?php
namespace NoccyLabs\SimpleJWT;
class JWTUtilTest extends \PHPUnit\Framework\TestCase
{
/**
* @covers \NoccyLabs\SimpleJWT\JWTUtil::encode
* @covers \NoccyLabs\SimpleJWT\JWTUtil::decode
*/
public function testTheEncodingShouldBeSymmetric()
{
$v1a = "HelloWorld";
$v1b = JWTUtil::encode($v1a);
$v1c = JWTUtil::decode($v1b);
$this->assertEquals($v1a, $v1c);
$this->assertNotEquals($v1a, $v1b);
}
}

View File

@ -1,38 +0,0 @@
<?php
namespace NoccyLabs\SimpleJwt;
use NoccyLabs\SimpleJwt\Key\JwtPlaintextKey;
class JwtTokenTest extends \PhpUnit\Framework\TestCase
{
public function testGeneratingTokens()
{
$key = new JwtPlaintextKey("test");
$tok = new JwtToken($key);
$tok->addClaim("foo", true);
$token = $tok->getSignedToken();
$this->assertNotNull($token);
$this->assertTrue($tok->isGenerated());
}
public function testParsingTokens()
{
$key = new JwtPlaintextKey("test");
$tok = new JwtToken($key);
$tok->addClaim("foo", true);
$token = $tok->getSignedToken();
$parsed = new JwtToken($key, $token);
$this->assertTrue($parsed->isValid());
$this->assertFalse($parsed->isGenerated());
}
}

View File

@ -1,18 +0,0 @@
<?php
namespace NoccyLabs\SimpleJwt;
class JwtUtilTest extends \PhpUnit\Framework\TestCase
{
public function testTheEncodingShouldBeSymmetric()
{
$v1a = "HelloWorld";
$v1b = JwtUtil::encode($v1a);
$v1c = JwtUtil::decode($v1b);
$this->assertEquals($v1a, $v1c);
$this->assertNotEquals($v1a, $v1b);
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace NoccyLabs\SimpleJWT\Key;
class JWTDerivedKeyTest extends \PHPUnit\Framework\TestCase
{
/**
* @covers \NoccyLabs\SimpleJWT\Key\JWTDerivedKey
*/
public function testTheDerivedKeysShouldBeConsistent()
{
$key1a = new JWTDerivedKey("foo", "foosalt");
$key1b = new JWTDerivedKey("foo", "foosalt");
$this->assertNotNull($key1a);
$this->assertEquals($key1a->getBinaryKey(), $key1b->getBinaryKey());
$key2a = new JWTDerivedKey("bar", "foosalt");
$key2b = new JWTDerivedKey("bar", "barsalt");
$key2c = new JWTDerivedKey("bar", "barsalt");
$this->assertNotNull($key2a);
$this->assertNotEquals($key2a->getBinaryKey(), $key2b->getBinaryKey());
$this->assertEquals($key2b->getBinaryKey(), $key2c->getBinaryKey());
}
/**
* @covers \NoccyLabs\SimpleJWT\Key\JWTDerivedKey
*/
public function testTheDerivedKeysShouldBeUnique()
{
$keys = [];
$keys[] = (new JWTDerivedKey("foo", "foosalt"))->getBinaryKey();
$keys[] = (new JWTDerivedKey("foo", "barsalt"))->getBinaryKey();
$keys[] = (new JWTDerivedKey("foo", "bazsalt"))->getBinaryKey();
$keys[] = (new JWTDerivedKey("bar", "foosalt"))->getBinaryKey();
$keys[] = (new JWTDerivedKey("bar", "barsalt"))->getBinaryKey();
$keys[] = (new JWTDerivedKey("bar", "bazsalt"))->getBinaryKey();
$unique = array_unique($keys);
$this->assertEquals(count($keys), count($unique));
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace NoccyLabs\SimpleJWT\Key;
class JWTPlaintextKeyTest extends \PHPUnit\Framework\TestCase
{
/**
* @covers \NoccyLabs\SimpleJWT\Key\JWTPlaintextKey
*/
public function testThePlaintextKeyShouldBeReturned()
{
$key = new JWTPlaintextKey("foo");
$this->assertEquals("foo", $key->getBinaryKey());
$key = new JWTPlaintextKey("bar");
$this->assertEquals("bar", $key->getBinaryKey());
}
}

View File

@ -1,37 +0,0 @@
<?php
namespace NoccyLabs\SimpleJwt\Key;
class JwtDerivedKeyTest extends \PhpUnit\Framework\TestCase
{
public function testTheDerivedKeysShouldBeConsistent()
{
$key1a = new JwtDerivedKey("foo", "foosalt");
$key1b = new JwtDerivedKey("foo", "foosalt");
$this->assertNotNull($key1a);
$this->assertEquals($key1a->getBinaryKey(), $key1b->getBinaryKey());
$key2a = new JwtDerivedKey("bar", "foosalt");
$key2b = new JwtDerivedKey("bar", "barsalt");
$key2c = new JwtDerivedKey("bar", "barsalt");
$this->assertNotNull($key2a);
$this->assertNotEquals($key2a->getBinaryKey(), $key2b->getBinaryKey());
$this->assertEquals($key2b->getBinaryKey(), $key2c->getBinaryKey());
}
public function testTheDerivedKeysShouldBeUnique()
{
$keys = [];
$keys[] = (new JwtDerivedKey("foo", "foosalt"))->getBinaryKey();
$keys[] = (new JwtDerivedKey("foo", "barsalt"))->getBinaryKey();
$keys[] = (new JwtDerivedKey("foo", "bazsalt"))->getBinaryKey();
$keys[] = (new JwtDerivedKey("bar", "foosalt"))->getBinaryKey();
$keys[] = (new JwtDerivedKey("bar", "barsalt"))->getBinaryKey();
$keys[] = (new JwtDerivedKey("bar", "bazsalt"))->getBinaryKey();
$unique = array_unique($keys);
$this->assertEquals(count($keys), count($unique));
}
}

View File

@ -1,16 +0,0 @@
<?php
namespace NoccyLabs\SimpleJwt\Key;
class JwtPlaintextKeyTest extends \PhpUnit\Framework\TestCase
{
public function testThePlaintextKeyShouldBeReturned()
{
$key = new JwtPlaintextKey("foo");
$this->assertEquals("foo", $key->getBinaryKey());
$key = new JwtPlaintextKey("bar");
$this->assertEquals("bar", $key->getBinaryKey());
}
}

View File

@ -0,0 +1,163 @@
<?php
namespace NoccyLabs\SimpleJWT\Validator;
use NoccyLabs\SimpleJWT\JWTToken;
use NoccyLabs\SimpleJWT\Key\JWTPlaintextKey;
class JWTValidatorTest extends \PHPUnit\Framework\TestCase
{
/**
* @covers \NoccyLabs\SimpleJWT\Key\JWTPlaintextKey
* @covers \NoccyLabs\SimpleJWT\JWTToken
* @covers \NoccyLabs\SimpleJWT\Validator\JWTValidator
* @covers NoccyLabs\SimpleJWT\Collection\PropertyBag
* @covers NoccyLabs\SimpleJWT\JWTUtil
*/
public function testValidTokensShouldPassWithDefaultConfiguration()
{
$key = new JWTPlaintextKey("key");
$token = new JWTToken($key);
$validator = new JWTValidator();
$valid = $validator->validateToken($token);
$this->assertEquals(true, $valid);
}
/**
* @covers \NoccyLabs\SimpleJWT\Validator\JWTValidator
* @covers \NoccyLabs\SimpleJWT\Key\JWTPlaintextKey
* @covers \NoccyLabs\SimpleJWT\JWTToken
* @covers \NoccyLabs\SimpleJWT\Validator\JWTTokenException
* @covers NoccyLabs\SimpleJWT\Collection\PropertyBag
* @covers NoccyLabs\SimpleJWT\JWTUtil
*/
public function testExpiredTokensShouldFailWithException()
{
$key = new JWTPlaintextKey("key");
$token = new JWTToken($key);
$token->header->set("exp", 0);
$token = new JWTToken($key, $token->getSignedToken());
$validator = new JWTValidator();
$this->expectException(JWTTokenException::class);
$valid = $validator->validateToken($token);
}
/**
* @covers \NoccyLabs\SimpleJWT\Validator\JWTValidator
* @covers \NoccyLabs\SimpleJWT\Key\JWTPlaintextKey
* @covers \NoccyLabs\SimpleJWT\JWTToken
* @covers \NoccyLabs\SimpleJWT\Validator\JWTTokenException
* @covers NoccyLabs\SimpleJWT\Collection\PropertyBag
* @covers NoccyLabs\SimpleJWT\JWTUtil
* @dataProvider tokenGenerator
*/
public function testPinningIssuer($issuer,$audience,$key,$token)
{
$goodIssuer = "a-dom.tld";
$jwtKey = new JWTPlaintextKey($key);
$jwtToken = new JWTToken($jwtKey, $token);
$validator = new JWTValidator();
$validator->requireIssuer($goodIssuer);
if ($goodIssuer != $issuer) {
$this->expectException(JWTTokenException::class);
}
$valid = $validator->validateToken($jwtToken);
if ($goodIssuer == $issuer) {
$this->assertTrue($valid);
}
}
/**
* @covers \NoccyLabs\SimpleJWT\Validator\JWTValidator
* @covers \NoccyLabs\SimpleJWT\Key\JWTPlaintextKey
* @covers \NoccyLabs\SimpleJWT\JWTToken
* @covers \NoccyLabs\SimpleJWT\Validator\JWTTokenException
* @covers NoccyLabs\SimpleJWT\Collection\PropertyBag
* @covers NoccyLabs\SimpleJWT\JWTUtil
* @dataProvider tokenGenerator
*/
public function testPinningAudience($issuer,$audience,$key,$token)
{
$goodAudience = [ "a-dom.tld", "app.a-dom.tld" ];
$jwtKey = new JWTPlaintextKey($key);
$jwtToken = new JWTToken($jwtKey, $token);
$validator = new JWTValidator();
$validator->requireAudience($goodAudience);
if (!in_array($audience, $goodAudience)) {
$this->expectException(JWTTokenException::class);
}
$valid = $validator->validateToken($jwtToken);
if (in_array($audience, $goodAudience)) {
$this->assertTrue($valid);
}
}
/**
* @covers \NoccyLabs\SimpleJWT\Validator\JWTValidator
* @covers \NoccyLabs\SimpleJWT\Key\JWTPlaintextKey
* @covers \NoccyLabs\SimpleJWT\JWTToken
* @covers \NoccyLabs\SimpleJWT\Validator\JWTTokenException
* @covers NoccyLabs\SimpleJWT\Collection\PropertyBag
* @covers NoccyLabs\SimpleJWT\JWTUtil
* @dataProvider tokenGenerator
*/
public function testPinningBoth($issuer,$audience,$key,$token)
{
$goodIssuer = "a-dom.tld";
$goodAudience = [ "a-dom.tld", "app.a-dom.tld" ];
$jwtKey = new JWTPlaintextKey($key);
$jwtToken = new JWTToken($jwtKey, $token);
$validator = new JWTValidator();
$validator->requireIssuer($goodIssuer);
$validator->requireAudience($goodAudience);
if (($goodIssuer != $issuer) || (!in_array($audience, $goodAudience))) {
$this->expectException(JWTTokenException::class);
}
$valid = $validator->validateToken($jwtToken);
if (($goodIssuer == $issuer) && (in_array($audience, $goodAudience))) {
$this->assertTrue($valid);
}
}
public static function tokenGenerator()
{
$keyrand = function () {
return substr(sha1(microtime(true).rand(0,65535)), 5, 10);
};
$token = function ($head,$claims,$key) {
$jwtKey = new JWTPlaintextKey($key);
$tok = new JWTToken($jwtKey);
$tok->header->setAll($head);
$tok->claims->setAll($claims);
return $tok->getSignedToken();
};
$row = function ($iss, $aud, array $claims) use ($keyrand, $token) {
$key = $keyrand();
$jwtKey = new JWTPlaintextKey($key);
return [
$iss,
$aud,
$key,
$token(['iss'=>$iss, 'aud'=>$aud], $claims, $key),
];
};
return [
$row("a-dom.tld", "a-dom.tld", []),
$row("b-dom.tld", "a-dom.tld", []),
$row("b-dom.tld", "b-dom.tld", []),
$row("a-dom.tld", "app.a-dom.tld", []),
$row("a-dom.tld", "app.b-dom.tld", []),
$row("", "app.b-dom.tld", []),
];
}
}

View File

@ -1,130 +0,0 @@
<?php
namespace NoccyLabs\SimpleJwt\Validator;
use NoccyLabs\SimpleJwt\JwtToken;
use NoccyLabs\SimpleJwt\Key\JwtPlaintextKey;
class JwtValidatorTest extends \PhpUnit\Framework\TestCase
{
public function testValidTokensShouldPassWithDefaultConfiguration()
{
$key = new JwtPlaintextKey("key");
$token = new JwtToken($key);
$validator = new JwtValidator();
$valid = $validator->validateToken($token);
$this->assertEquals(true, $valid);
}
public function testExpiredTokensShouldFailWithException()
{
$key = new JwtPlaintextKey("key");
$token = new JwtToken($key);
$token->header->set("exp", 0);
$token = new JwtToken($key, $token->getSignedToken());
$validator = new JwtValidator();
$this->expectException(JwtTokenException::class);
$valid = $validator->validateToken($token);
}
/**
* @dataProvider tokenGenerator
*/
public function testPinningIssuer($issuer,$audience,$key,$token)
{
$goodIssuer = "a-dom.tld";
$jwtKey = new JwtPlaintextKey($key);
$jwtToken = new JwtToken($jwtKey, $token);
$validator = new JwtValidator();
$validator->requireIssuer($goodIssuer);
if ($goodIssuer != $issuer) {
$this->expectException(JwtTokenException::class);
}
$valid = $validator->validateToken($jwtToken);
if ($goodIssuer == $issuer) {
$this->assertTrue($valid);
}
}
/**
* @dataProvider tokenGenerator
*/
public function testPinningAudience($issuer,$audience,$key,$token)
{
$goodAudience = [ "a-dom.tld", "app.a-dom.tld" ];
$jwtKey = new JwtPlaintextKey($key);
$jwtToken = new JwtToken($jwtKey, $token);
$validator = new JwtValidator();
$validator->requireAudience($goodAudience);
if (!in_array($audience, $goodAudience)) {
$this->expectException(JwtTokenException::class);
}
$valid = $validator->validateToken($jwtToken);
if (in_array($audience, $goodAudience)) {
$this->assertTrue($valid);
}
}
/**
* @dataProvider tokenGenerator
*/
public function testPinningBoth($issuer,$audience,$key,$token)
{
$goodIssuer = "a-dom.tld";
$goodAudience = [ "a-dom.tld", "app.a-dom.tld" ];
$jwtKey = new JwtPlaintextKey($key);
$jwtToken = new JwtToken($jwtKey, $token);
$validator = new JwtValidator();
$validator->requireIssuer($goodIssuer);
$validator->requireAudience($goodAudience);
if (($goodIssuer != $issuer) || (!in_array($audience, $goodAudience))) {
$this->expectException(JwtTokenException::class);
}
$valid = $validator->validateToken($jwtToken);
if (($goodIssuer == $issuer) && (in_array($audience, $goodAudience))) {
$this->assertTrue($valid);
}
}
public function tokenGenerator()
{
$keyrand = function () {
return substr(sha1(microtime(true).rand(0,65535)), 5, 10);
};
$token = function ($head,$claims,$key) {
$jwtKey = new JwtPlaintextKey($key);
$tok = new JwtToken($jwtKey);
$tok->header->setAll($head);
$tok->claims->setAll($claims);
return $tok->getSignedToken();
};
$row = function ($iss, $aud, array $claims) use ($keyrand, $token) {
$key = $keyrand();
$jwtKey = new JwtPlaintextKey($key);
return [
$iss,
$aud,
$key,
$token(['iss'=>$iss, 'aud'=>$aud], $claims, $key),
];
};
return [
$row("a-dom.tld", "a-dom.tld", []),
$row("b-dom.tld", "a-dom.tld", []),
$row("b-dom.tld", "b-dom.tld", []),
$row("a-dom.tld", "app.a-dom.tld", []),
$row("a-dom.tld", "app.b-dom.tld", []),
$row("", "app.b-dom.tld", []),
];
}
}