diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..340d96b0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.buildpath +.project +.settings/ +vendor +build diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..530e7f31 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: php +php: + - 5.5 +before_script: + - composer selfupdate + - composer install --prefer-dist -o diff --git a/README.md b/README.md index 0317057f..97deb417 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,47 @@ -jwt -=== +# JWT -A simple library to work with JSON Web Token and JSON Web Signature +master [![Build Status](https://secure.travis-ci.org/lcobucci/jwt.png?branch=master)](http://travis-ci.org/#!/lcobucci/jwt) +develop [![Build Status](https://secure.travis-ci.org/lcobucci/jwt.png?branch=develop)](http://travis-ci.org/#!/lcobucci/jwt) + +[![Total Downloads](https://poser.pugx.org/lcobucci/jwt/downloads.png)](https://packagist.org/packages/lcobucci/jwt) +[![Latest Stable Version](https://poser.pugx.org/lcobucci/jwt/v/stable.png)](https://packagist.org/packages/lcobucci/jwt) + +A simple library to work with JSON Web Token and JSON Web Signature (requires PHP 5.5+) + +## Instalation + +Just add to your composer.json: ```"lcobucci/jwt": "1.x"``` + +## Basic usage + +### Creating + +Just use the builder to create a new JWT/JWS tokens: + +```php +setIssuer('http://example.com') // Configures the issuer (iss claim) + ->setAudience('http://example.org') // Configures the audience (aud claim) + ->setId('4f1g23a12aa', true) // Configures the id (jti claim), replicating as a header item + ->set('uid', 1) // Configures a new claim, called "uid" + ->sign(new Sha256(), 'my key') // Signs the token with HS256 using "my key" as key + ->getToken(); // Retrieves the generated token + +echo $token; // The string representation of the object is a JWT string (pretty easy, right?) +``` +### Parsing from strings + +Use the parser to create a new token from a JWT string: + +```php +parse('...'); // Parses from a string +$token->getHeader(); // Retrieves the token header +$token->getClaims(); // Retrieves the token claims +$token->verify('my key'); // Verifies if the signature was created with given key (if token is signed) +``` \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 00000000..bc97fbbe --- /dev/null +++ b/composer.json @@ -0,0 +1,27 @@ +{ + "name" : "lcobucci/jwt", + "description" : "A simple library to work with JSON Web Token and JSON Web Signature", + "type" : "library", + "authors" : [{ + "name" : "Luís Otávio Cobucci Oblonczyk", + "email" : "lcobucci@gmail.com", + "role": "Developer" + } + ], + "keywords" : ["JWT", "JWS"], + "license" : ["BSD-3-Clause"], + "require" : { + "php" : ">=5.5" + }, + "require-dev" : { + "phpunit/phpunit" : "4.0.x", + "squizlabs/php_codesniffer" : "*", + "phpmd/phpmd" : "*" + }, + "autoload" : { + "psr-4" : { + "Lcobucci\\JWT\\" : "src", + "Lcobucci\\JWT\\Test\\" : "test" + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 00000000..f3320231 --- /dev/null +++ b/composer.lock @@ -0,0 +1,783 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" + ], + "hash": "1c0271b44549953a627d59a27ad460ee", + "packages": [ + + ], + "packages-dev": [ + { + "name": "pdepend/pdepend", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/pdepend/pdepend.git", + "reference": "1537f19d62d7b30c13ac173270106df7c6b9c459" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pdepend/pdepend/zipball/1537f19d62d7b30c13ac173270106df7c6b9c459", + "reference": "1537f19d62d7b30c13ac173270106df7c6b9c459", + "shasum": "" + }, + "require": { + "php": ">=5.2.3" + }, + "bin": [ + "src/bin/pdepend" + ], + "type": "library", + "autoload": { + "psr-0": { + "PHP_": "src/main/php/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Official version of pdepend to be handled with Composer", + "time": "2013-12-04 17:46:00" + }, + { + "name": "phpmd/phpmd", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/phpmd/phpmd.git", + "reference": "692b7b1b64518091b2b1bea91b489dbb13598c07" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpmd/phpmd/zipball/692b7b1b64518091b2b1bea91b489dbb13598c07", + "reference": "692b7b1b64518091b2b1bea91b489dbb13598c07", + "shasum": "" + }, + "require": { + "pdepend/pdepend": ">=1.1.1", + "php": ">=5.3.0" + }, + "bin": [ + "src/bin/phpmd" + ], + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "../../pdepend/pdepend/src/main/php", + "src/main/php" + ], + "description": "Official version of PHPMD handled with Composer.", + "time": "2013-07-26 14:47:02" + }, + { + "name": "phpunit/php-code-coverage", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "23e6ac9513df2af67f9f713347f3e4bf4b59784c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/23e6ac9513df2af67f9f713347f3e4bf4b59784c", + "reference": "23e6ac9513df2af67f9f713347f3e4bf4b59784c", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": "~1.3.1", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "~1.1.3", + "sebastian/environment": "~1.0", + "sebastian/version": "~1.0.3" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": ">=4.0.0,<4.1.0" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.2.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2014-03-07 16:03:14" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.3.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/acd690379117b042d1c8af1fafd61bde001bf6bb", + "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "File/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2013-10-10 15:34:57" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", + "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "Text/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2014-01-30 17:20:04" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/19689d4354b295ee3d8c54b4f42c3efb69cbc17c", + "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "PHP/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2013-08-02 07:42:54" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.1.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "66a29f0c5bdf85e70112f25ec9072efec5e2d426" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/66a29f0c5bdf85e70112f25ec9072efec5e2d426", + "reference": "66a29f0c5bdf85e70112f25ec9072efec5e2d426", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "PHP/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2013-08-02 19:10:55" + }, + { + "name": "phpunit/phpunit", + "version": "4.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "6a0c2dbfd79ddb5072d77fb5879c8045976b5686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6a0c2dbfd79ddb5072d77fb5879c8045976b5686", + "reference": "6a0c2dbfd79ddb5072d77fb5879c8045976b5686", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.3.3", + "phpunit/php-code-coverage": ">=2.0.0,<2.1.0", + "phpunit/php-file-iterator": "~1.3.1", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "~1.0.2", + "phpunit/phpunit-mock-objects": ">=2.0.0,<2.1.0", + "sebastian/diff": "~1.1", + "sebastian/environment": "~1.0", + "sebastian/exporter": "~1.0.1", + "sebastian/version": "~1.0.3", + "symfony/yaml": "~2.0" + }, + "suggest": { + "ext-json": "*", + "ext-simplexml": "*", + "ext-tokenizer": "*", + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "", + "../../symfony/yaml/" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "http://www.phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2014-03-07 18:08:58" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "f6942d1da56abcf0ddd11e94a24dfbeb11777a9d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/f6942d1da56abcf0ddd11e94a24dfbeb11777a9d", + "reference": "f6942d1da56abcf0ddd11e94a24dfbeb11777a9d", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-text-template": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": ">=4.0.0,<4.1.0" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2014-03-07 17:55:21" + }, + { + "name": "sebastian/diff", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "1e091702a5a38e6b4c1ba9ca816e3dd343df2e2d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/1e091702a5a38e6b4c1ba9ca816e3dd343df2e2d", + "reference": "1e091702a5a38e6b4c1ba9ca816e3dd343df2e2d", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "http://www.github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2013-08-03 16:46:33" + }, + { + "name": "sebastian/environment", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "79517609ec01139cd7e9fded0dd7ce08c952ef6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/79517609ec01139cd7e9fded0dd7ce08c952ef6a", + "reference": "79517609ec01139cd7e9fded0dd7ce08c952ef6a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "4.0.*@dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2014-02-18 16:17:19" + }, + { + "name": "sebastian/exporter", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "1f9a98e6f5dfe0524cb8c6166f7c82f3e9ae1529" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/1f9a98e6f5dfe0524cb8c6166f7c82f3e9ae1529", + "reference": "1f9a98e6f5dfe0524cb8c6166f7c82f3e9ae1529", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "4.0.*@dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2014-02-16 08:26:31" + }, + { + "name": "sebastian/version", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43", + "reference": "b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2014-03-07 15:35:33" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "1.5.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "a76a39b317ce8106abe6264daa505e24e1731860" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/a76a39b317ce8106abe6264daa505e24e1731860", + "reference": "a76a39b317ce8106abe6264daa505e24e1731860", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.1.2" + }, + "suggest": { + "phpunit/php-timer": "dev-master" + }, + "bin": [ + "scripts/phpcs" + ], + "type": "library", + "autoload": { + "classmap": [ + "CodeSniffer.php", + "CodeSniffer/CLI.php", + "CodeSniffer/Exception.php", + "CodeSniffer/File.php", + "CodeSniffer/Report.php", + "CodeSniffer/Reporting.php", + "CodeSniffer/Sniff.php", + "CodeSniffer/Tokens.php", + "CodeSniffer/Reports/", + "CodeSniffer/CommentParser/", + "CodeSniffer/Tokenizers/", + "CodeSniffer/DocGenerators/", + "CodeSniffer/Standards/AbstractPatternSniff.php", + "CodeSniffer/Standards/AbstractScopeSniff.php", + "CodeSniffer/Standards/AbstractVariableSniff.php", + "CodeSniffer/Standards/IncorrectPatternException.php", + "CodeSniffer/Standards/Generic/Sniffs/", + "CodeSniffer/Standards/MySource/Sniffs/", + "CodeSniffer/Standards/PEAR/Sniffs/", + "CodeSniffer/Standards/PSR1/Sniffs/", + "CodeSniffer/Standards/PSR2/Sniffs/", + "CodeSniffer/Standards/Squiz/Sniffs/", + "CodeSniffer/Standards/Zend/Sniffs/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenises PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2014-02-04 23:49:58" + }, + { + "name": "symfony/yaml", + "version": "v2.4.2", + "target-dir": "Symfony/Component/Yaml", + "source": { + "type": "git", + "url": "https://github.com/symfony/Yaml.git", + "reference": "bb6ddaf8956139d1b8c360b4b713ed0138e876b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Yaml/zipball/bb6ddaf8956139d1b8c360b4b713ed0138e876b3", + "reference": "bb6ddaf8956139d1b8c360b4b713ed0138e876b3", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Yaml\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "http://symfony.com", + "time": "2014-01-07 13:28:54" + } + ], + "aliases": [ + + ], + "minimum-stability": "stable", + "stability-flags": [ + + ], + "platform": { + "php": ">=5.5" + }, + "platform-dev": [ + + ] +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 00000000..ed065180 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,23 @@ + + + + + test + + + + + + src + + + + vendor + + + diff --git a/src/Builder.php b/src/Builder.php new file mode 100644 index 00000000..82b08320 --- /dev/null +++ b/src/Builder.php @@ -0,0 +1,235 @@ + + * @since 0.1.0 + */ +class Builder +{ + /** + * The token header + * + * @var array + */ + private $header; + + /** + * The token claim set + * + * @var array + */ + private $claims; + + /** + * The token signature + * + * @var Signature + */ + private $signature; + + /** + * The data encoder + * + * @var Encoder + */ + private $encoder; + + /** + * Initializes a new builder + */ + public function __construct(Encoder $encoder = null) + { + $this->encoder = $encoder ?: new Encoder(); + $this->header = ['typ'=> 'JWT', 'alg' => 'none']; + $this->claims = []; + } + + /** + * Configures the audience + * + * @param string $audience + * @param boolean $replicateAsHeader + * + * @return Builder + */ + public function setAudience($audience, $replicateAsHeader = false) + { + return $this->setRegisteredClaim('aud', (string) $audience, $replicateAsHeader); + } + + /** + * Configures the expiration time + * + * @param int $expiration + * @param boolean $replicateAsHeader + * + * @return Builder + */ + public function setExpiration($expiration, $replicateAsHeader = false) + { + return $this->setRegisteredClaim('exp', (int) $expiration, $replicateAsHeader); + } + + /** + * Configures the token id + * + * @param string $id + * @param boolean $replicateAsHeader + * + * @return Builder + */ + public function setId($id, $replicateAsHeader = false) + { + return $this->setRegisteredClaim('jti', (string) $id, $replicateAsHeader); + } + + /** + * Configures the time that the token was issued + * + * @param int $issuedAt + * @param boolean $replicateAsHeader + * + * @return Builder + */ + public function setIssueAt($issuedAt, $replicateAsHeader = false) + { + return $this->setRegisteredClaim('iat', (int) $issuedAt, $replicateAsHeader); + } + + /** + * Configures the issuer + * + * @param string $issuer + * @param boolean $replicateAsHeader + * + * @return Builder + */ + public function setIssuer($issuer, $replicateAsHeader = false) + { + return $this->setRegisteredClaim('iss', (string) $issuer, $replicateAsHeader); + } + + /** + * Configures the time before which the token cannot be accepted + * + * @param int $notBefore + * @param boolean $replicateAsHeader + * + * @return Builder + */ + public function setNotBefore($notBefore, $replicateAsHeader = false) + { + return $this->setRegisteredClaim('nbf', (int) $notBefore, $replicateAsHeader); + } + + /** + * Configures the subject + * + * @param string $subject + * @param boolean $replicateAsHeader + * + * @return Builder + */ + public function setSubject($subject, $replicateAsHeader = false) + { + return $this->setRegisteredClaim('sub', (string) $subject, $replicateAsHeader); + } + + /** + * Configures a registed claim + * + * @param string $name + * @param mixed $value + * @param boolean $replicate + * + * @return Builder + */ + protected function setRegisteredClaim($name, $value, $replicate) + { + $this->set($name, $value); + + if ($replicate) { + $this->header[$name] = $value; + } + + return $this; + } + + /** + * Configures a claim item + * + * @param string $name + * @param mixed $value + * + * @return Builder + * + * @throws BadMethodCallException When data has been already signed + */ + public function set($name, $value) + { + if ($this->signature) { + throw new BadMethodCallException('You must unsign before make changes'); + } + + $this->claims[(string) $name] = $value; + + return $this; + } + + /** + * Signs the data + * + * @param Signer $signer + * @param string $key + * + * @return Builder + */ + public function sign(Signer $signer, $key) + { + $signer->modifyHeader($this->header); + + $this->signature = $signer->sign( + $this->getToken()->getPayload(), + $key + ); + + return $this; + } + + /** + * Removes the signature from the builder + * + * @return Builder + */ + public function unsign() + { + $this->signature = null; + + return $this; + } + + /** + * Returns the resultant token + * + * @return Token + */ + public function getToken() + { + $token = new Token($this->header, $this->claims, $this->signature); + $token->setEncoder($this->encoder); + + return $token; + } +} diff --git a/src/Parser.php b/src/Parser.php new file mode 100644 index 00000000..ebee35dd --- /dev/null +++ b/src/Parser.php @@ -0,0 +1,164 @@ + + * @since 0.1.0 + */ +class Parser +{ + /** + * The data encoder + * + * @var Encoder + */ + private $encoder; + + /** + * The data decoder + * + * @var Decoder + */ + private $decoder; + + /** + * The signer factory + * + * @var Factory + */ + private $factory; + + /** + * Initializes the object + * + * @param Encoder $encoder + * @param Decoder $decoder + * @param Factory $factory + */ + public function __construct( + Encoder $encoder = null, + Decoder $decoder = null, + Factory $factory = null + ) { + $this->encoder = $encoder ?: new Encoder(); + $this->decoder = $decoder ?: new Decoder(); + $this->factory = $factory ?: new Factory(); + } + + /** + * Parses the JWT and returns a token + * + * @param string $jwt + * @return Token + */ + public function parse($jwt) + { + $data = $this->splitJwt($jwt); + + $token = new Token( + $header = $this->parseHeader($data[0]), + $this->parseClaims($data[1]), + $this->parseSignature($header, $data[2]) + ); + + $token->setEncoder($this->encoder); + + return $token; + } + + /** + * Slipts 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 new InvalidArgumentException('The JWT string must have two dots'); + } + + $data = explode('.', $jwt); + + if (count($data) != 3) { + throw new InvalidArgumentException('The JWT string must have two dots'); + } + + return $data; + } + + /** + * Parses the header from a string + * + * @param string $data + * + * @return array + * + * @throws InvalidArgumentException When an invalid header is informed + */ + protected function parseHeader($data) + { + $header = $this->decoder->jsonDecode($this->decoder->base64UrlDecode($data)); + + if (!is_array($header) || isset($header['enc'])) { + throw new InvalidArgumentException('That header is not a valid array or uses encryption'); + } + + return $header; + } + + /** + * Parses the claim set from a string + * + * @param string $data + * + * @return array + * + * @throws InvalidArgumentException When an invalid claim set is informed + */ + protected function parseClaims($data) + { + $claims = $this->decoder->jsonDecode($this->decoder->base64UrlDecode($data)); + + if (!is_array($claims)) { + throw new InvalidArgumentException('That claims are not valid'); + } + + return $claims; + } + + /** + * 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 null; + } + + $hash = $this->decoder->base64UrlDecode($data); + + return new Signature($this->factory->create($header['alg']), $hash); + } +} diff --git a/src/Parsing/Decoder.php b/src/Parsing/Decoder.php new file mode 100644 index 00000000..dffee87d --- /dev/null +++ b/src/Parsing/Decoder.php @@ -0,0 +1,56 @@ + + * @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) + { + $data = json_decode($json, true); + + if (json_last_error() != JSON_ERROR_NONE) { + throw new RuntimeException('Error while decoding to JSON: ' . json_last_error_msg()); + } + + return $data; + } + + /** + * 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, '-_', '+/')); + } +} diff --git a/src/Parsing/Encoder.php b/src/Parsing/Encoder.php new file mode 100644 index 00000000..ece5b286 --- /dev/null +++ b/src/Parsing/Encoder.php @@ -0,0 +1,51 @@ + + * @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) + { + $json = json_encode($data); + + if (json_last_error() != JSON_ERROR_NONE) { + throw new RuntimeException('Error while encoding to JSON: ' . json_last_error_msg()); + } + + return $json; + } + + /** + * Encodes to base64url + * + * @param string $data + * @return string + */ + public function base64UrlEncode($data) + { + return str_replace('=', '', strtr(base64_encode($data), '+/', '-_')); + } +} diff --git a/src/Signature.php b/src/Signature.php new file mode 100644 index 00000000..618faf83 --- /dev/null +++ b/src/Signature.php @@ -0,0 +1,67 @@ + + * @since 0.1.0 + */ +class Signature +{ + /** + * The signer that created this signature + * + * @var Signer + */ + protected $signer; + + /** + * The resultant hash + * + * @var string + */ + protected $hash; + + /** + * Initializes the object + * + * @param Signer $signer + * @param string $hash + */ + public function __construct(Signer $signer, $hash) + { + $this->signer = $signer; + $this->hash = $hash; + } + + /** + * Verifies if the current hash matches with with the result of the creation of + * a new signature with given data + * + * @param string $payload + * @param string $key + * + * @return boolean + */ + public function verify($payload, $key) + { + return $this->hash == $this->signer->createHash($payload, $key); + } + + /** + * Returns the current hash as a string representation of the signature + * + * @return string + */ + public function __toString() + { + return $this->hash; + } +} diff --git a/src/Signer.php b/src/Signer.php new file mode 100644 index 00000000..b9398580 --- /dev/null +++ b/src/Signer.php @@ -0,0 +1,51 @@ + + * @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 string $key + * + * @return Signature + */ + public function sign($payload, $key); + + /** + * Creates a hash with the given data + * + * @param string $payload + * @param string $key + * + * @return string + */ + public function createHash($payload, $key); +} diff --git a/src/Signer/BaseSigner.php b/src/Signer/BaseSigner.php new file mode 100644 index 00000000..4de881d4 --- /dev/null +++ b/src/Signer/BaseSigner.php @@ -0,0 +1,37 @@ + + * @since 0.1.0 + */ +abstract class BaseSigner implements Signer +{ + /** + * {@inheritdoc} + */ + public function modifyHeader(array &$headers) + { + $headers['typ'] = 'JWS'; + $headers['alg'] = $this->getAlgorithmId(); + } + + /** + * {@inheritdoc} + */ + public function sign($payload, $key) + { + return new Signature($this, $this->createHash($payload, $key)); + } +} diff --git a/src/Signer/Factory.php b/src/Signer/Factory.php new file mode 100644 index 00000000..2921c72a --- /dev/null +++ b/src/Signer/Factory.php @@ -0,0 +1,46 @@ + + * @since 0.1.0 + */ +class Factory +{ + /** + * Retrives a signer instance + * + * @param string $id + * + * @return Signer + * + * @throws InvalidArgumentException When signer is not implemented or invalid + */ + public function create($id) + { + if ($id === 'HS256') { + return new Sha256(); + } + + if ($id === 'HS384') { + return new Sha384(); + } + + if ($id === 'HS512') { + return new Sha512(); + } + + throw new InvalidArgumentException('Invalid signer'); + } +} diff --git a/src/Signer/Hmac.php b/src/Signer/Hmac.php new file mode 100644 index 00000000..7bf971e4 --- /dev/null +++ b/src/Signer/Hmac.php @@ -0,0 +1,32 @@ + + * @since 0.1.0 + */ +abstract class Hmac extends BaseSigner +{ + /** + * {@inheritdoc} + */ + public function createHash($payload, $key) + { + return hash_hmac($this->getAlgorithm(), $payload, $key, true); + } + + /** + * Returns the algorithm name + * + * @return string + */ + abstract public function getAlgorithm(); +} diff --git a/src/Signer/Sha256.php b/src/Signer/Sha256.php new file mode 100644 index 00000000..0984d411 --- /dev/null +++ b/src/Signer/Sha256.php @@ -0,0 +1,33 @@ + + * @since 0.1.0 + */ +class Sha256 extends Hmac +{ + /** + * {@inheritdoc} + */ + public function getAlgorithmId() + { + return 'HS256'; + } + + /** + * {@inheritdoc} + */ + public function getAlgorithm() + { + return 'sha256'; + } +} diff --git a/src/Signer/Sha384.php b/src/Signer/Sha384.php new file mode 100644 index 00000000..6efbd838 --- /dev/null +++ b/src/Signer/Sha384.php @@ -0,0 +1,33 @@ + + * @since 0.1.0 + */ +class Sha384 extends Hmac +{ + /** + * {@inheritdoc} + */ + public function getAlgorithmId() + { + return 'HS384'; + } + + /** + * {@inheritdoc} + */ + public function getAlgorithm() + { + return 'sha384'; + } +} diff --git a/src/Signer/Sha512.php b/src/Signer/Sha512.php new file mode 100644 index 00000000..a5de15bb --- /dev/null +++ b/src/Signer/Sha512.php @@ -0,0 +1,33 @@ + + * @since 0.1.0 + */ +class Sha512 extends Hmac +{ + /** + * {@inheritdoc} + */ + public function getAlgorithmId() + { + return 'HS512'; + } + + /** + * {@inheritdoc} + */ + public function getAlgorithm() + { + return 'sha512'; + } +} diff --git a/src/Token.php b/src/Token.php new file mode 100644 index 00000000..f7b8d73e --- /dev/null +++ b/src/Token.php @@ -0,0 +1,160 @@ + + * @since 0.1.0 + */ +class Token +{ + /** + * The token header + * + * @var array + */ + private $header; + + /** + * The token claim set + * + * @var array + */ + private $claims; + + /** + * The token signature + * + * @var Signature + */ + private $signature; + + /** + * The data encoder + * + * @var Encoder + */ + private $encoder; + + /** + * Initializes the object + * + * @param array $header + * @param array $claims + * @param Signature $signature + */ + public function __construct(array $header = ['alg' => 'none'], array $claims = [], Signature $signature = null) + { + $this->header = $header; + $this->claims = $claims; + $this->signature = $signature; + } + + /** + * Configures the data encoder + * + * @param Encoder $encoder + */ + public function setEncoder(Encoder $encoder) + { + $this->encoder = $encoder; + } + + /** + * Returns the token header + * + * @return array + */ + public function getHeader() + { + return $this->header; + } + + /** + * Returns the token claim set + * + * @return array + */ + public function getClaims() + { + return $this->claims; + } + + /** + * Returns the token signature + * + * @return Signature + */ + public function getSignature() + { + return $this->signature; + } + + /** + * Verify if the key matches with the one that created the signature + * + * @param string $key + * + * @return boolean + * + * @throws BadMethodCallException When token is not signed + */ + public function verify($key) + { + if ($this->signature === null) { + throw new BadMethodCallException('This token is not signed'); + } + + return $this->signature->verify($this->getPayload(), $key); + } + + /** + * Returns the token payload + * + * @return string + * + * @throws BadMethodCallException When $this->encoder is not configured + */ + public function getPayload() + { + if ($this->encoder === null) { + throw new BadMethodCallException('Encoder must be configured'); + } + + return sprintf( + '%s.%s', + $this->encoder->base64UrlEncode($this->encoder->jsonEncode($this->header)), + $this->encoder->base64UrlEncode($this->encoder->jsonEncode($this->claims)) + ); + } + + /** + * Returns an encoded representation of the token + * + * @return string + */ + public function __toString() + { + try { + $data = $this->getPayload() . '.'; + + if ($this->signature) { + $data .= $this->encoder->base64UrlEncode($this->signature); + } + + return $data; + } catch (BadMethodCallException $e) { + return ''; + } + } +} diff --git a/test/BuilderTest.php b/test/BuilderTest.php new file mode 100644 index 00000000..48491639 --- /dev/null +++ b/test/BuilderTest.php @@ -0,0 +1,533 @@ + + * @since 0.1.0 + * + * @coversDefaultClass Lcobucci\JWT\Builder + */ +class BuilderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var Encoder|\PHPUnit_Framework_MockObject_MockObject + */ + protected $encoder; + + /** + * {@inheritdoc} + */ + protected function setUp() + { + $this->encoder = $this->getMockBuilder(Encoder::class) + ->setMockClassName('EncoderMock') + ->getMock(); + } + + /** + * @test + * @covers ::__construct + */ + public function constructMustInitializeTheAttributes() + { + $builder = new Builder($this->encoder); + + $this->assertAttributeEquals(['alg' => 'none', 'typ' => 'JWT'], 'header', $builder); + $this->assertAttributeEquals([], 'claims', $builder); + $this->assertAttributeEquals(null, 'signature', $builder); + $this->assertAttributeSame($this->encoder, 'encoder', $builder); + } + + /** + * @test + * @covers ::__construct + * @covers ::setAudience + * @covers ::setRegisteredClaim + * @covers ::set + */ + public function setAudienceMustChangeTheAudClaim() + { + $builder = new Builder($this->encoder); + $builder->setAudience('test'); + + $this->assertAttributeEquals(['alg' => 'none', 'typ' => 'JWT'], 'header', $builder); + $this->assertAttributeEquals(['aud' => 'test'], 'claims', $builder); + } + + /** + * @test + * @covers ::__construct + * @covers ::setAudience + * @covers ::setRegisteredClaim + * @covers ::set + */ + public function setAudienceCanReplicateItemOnHeader() + { + $builder = new Builder($this->encoder); + $builder->setAudience('test', true); + + $this->assertAttributeEquals(['alg' => 'none', 'typ' => 'JWT', 'aud' => 'test'], 'header', $builder); + $this->assertAttributeEquals(['aud' => 'test'], 'claims', $builder); + } + + /** + * @test + * @covers ::__construct + * @covers ::setAudience + * @covers ::setRegisteredClaim + * @covers ::set + */ + public function setAudienceMustKeepAFluentInterface() + { + $builder = new Builder($this->encoder); + + $this->assertSame($builder, $builder->setAudience('test')); + } + + /** + * @test + * @covers ::__construct + * @covers ::setExpiration + * @covers ::setRegisteredClaim + * @covers ::set + */ + public function setExpirationMustChangeTheExpClaim() + { + $builder = new Builder($this->encoder); + $builder->setExpiration('2'); + + $this->assertAttributeEquals(['alg' => 'none', 'typ' => 'JWT'], 'header', $builder); + $this->assertAttributeEquals(['exp' => 2], 'claims', $builder); + } + + /** + * @test + * @covers ::__construct + * @covers ::setExpiration + * @covers ::setRegisteredClaim + * @covers ::set + */ + public function setExpirationCanReplicateItemOnHeader() + { + $builder = new Builder($this->encoder); + $builder->setExpiration('2', true); + + $this->assertAttributeEquals(['alg' => 'none', 'typ' => 'JWT', 'exp' => 2], 'header', $builder); + $this->assertAttributeEquals(['exp' => 2], 'claims', $builder); + } + + /** + * @test + * @covers ::__construct + * @covers ::setExpiration + * @covers ::setRegisteredClaim + * @covers ::set + */ + public function setExpirationMustKeepAFluentInterface() + { + $builder = new Builder($this->encoder); + + $this->assertSame($builder, $builder->setExpiration('2')); + } + + /** + * @test + * @covers ::__construct + * @covers ::setId + * @covers ::setRegisteredClaim + * @covers ::set + */ + public function setIdMustChangeTheJtiClaim() + { + $builder = new Builder($this->encoder); + $builder->setId('2'); + + $this->assertAttributeEquals(['alg' => 'none', 'typ' => 'JWT'], 'header', $builder); + $this->assertAttributeEquals(['jti' => '2'], 'claims', $builder); + } + + /** + * @test + * @covers ::__construct + * @covers ::setId + * @covers ::setRegisteredClaim + * @covers ::set + */ + public function setIdCanReplicateItemOnHeader() + { + $builder = new Builder($this->encoder); + $builder->setId('2', true); + + $this->assertAttributeEquals(['alg' => 'none', 'typ' => 'JWT', 'jti' => '2'], 'header', $builder); + $this->assertAttributeEquals(['jti' => '2'], 'claims', $builder); + } + + /** + * @test + * @covers ::__construct + * @covers ::setId + * @covers ::setRegisteredClaim + * @covers ::set + */ + public function setIdMustKeepAFluentInterface() + { + $builder = new Builder($this->encoder); + + $this->assertSame($builder, $builder->setId('2')); + } + + /** + * @test + * @covers ::__construct + * @covers ::setIssueAt + * @covers ::setRegisteredClaim + * @covers ::set + */ + public function setIssueAtMustChangeTheIatClaim() + { + $builder = new Builder($this->encoder); + $builder->setIssueAt('2'); + + $this->assertAttributeEquals(['alg' => 'none', 'typ' => 'JWT'], 'header', $builder); + $this->assertAttributeEquals(['iat' => 2], 'claims', $builder); + } + + /** + * @test + * @covers ::__construct + * @covers ::setIssueAt + * @covers ::setRegisteredClaim + * @covers ::set + */ + public function setIssueAtCanReplicateItemOnHeader() + { + $builder = new Builder($this->encoder); + $builder->setIssueAt('2', true); + + $this->assertAttributeEquals(['alg' => 'none', 'typ' => 'JWT', 'iat' => 2], 'header', $builder); + $this->assertAttributeEquals(['iat' => 2], 'claims', $builder); + } + + /** + * @test + * @covers ::__construct + * @covers ::setIssueAt + * @covers ::setRegisteredClaim + * @covers ::set + */ + public function setIssueAtMustKeepAFluentInterface() + { + $builder = new Builder($this->encoder); + + $this->assertSame($builder, $builder->setIssueAt('2')); + } + + /** + * @test + * @covers ::__construct + * @covers ::setIssuer + * @covers ::setRegisteredClaim + * @covers ::set + */ + public function setIssuerMustChangeTheIssClaim() + { + $builder = new Builder($this->encoder); + $builder->setIssuer('2'); + + $this->assertAttributeEquals(['alg' => 'none', 'typ' => 'JWT'], 'header', $builder); + $this->assertAttributeEquals(['iss' => '2'], 'claims', $builder); + } + + /** + * @test + * @covers ::__construct + * @covers ::setIssuer + * @covers ::setRegisteredClaim + * @covers ::set + */ + public function setIssuerCanReplicateItemOnHeader() + { + $builder = new Builder($this->encoder); + $builder->setIssuer('2', true); + + $this->assertAttributeEquals(['alg' => 'none', 'typ' => 'JWT', 'iss' => '2'], 'header', $builder); + $this->assertAttributeEquals(['iss' => '2'], 'claims', $builder); + } + + /** + * @test + * @covers ::__construct + * @covers ::setIssuer + * @covers ::setRegisteredClaim + * @covers ::set + */ + public function setIssuerMustKeepAFluentInterface() + { + $builder = new Builder($this->encoder); + + $this->assertSame($builder, $builder->setIssuer('2')); + } + + /** + * @test + * @covers ::__construct + * @covers ::setNotBefore + * @covers ::setRegisteredClaim + * @covers ::set + */ + public function setNotBeforeMustChangeTheNbfClaim() + { + $builder = new Builder($this->encoder); + $builder->setNotBefore('2'); + + $this->assertAttributeEquals(['alg' => 'none', 'typ' => 'JWT'], 'header', $builder); + $this->assertAttributeEquals(['nbf' => 2], 'claims', $builder); + } + + /** + * @test + * @covers ::__construct + * @covers ::setNotBefore + * @covers ::setRegisteredClaim + * @covers ::set + */ + public function setNotBeforeCanReplicateItemOnHeader() + { + $builder = new Builder($this->encoder); + $builder->setNotBefore('2', true); + + $this->assertAttributeEquals(['alg' => 'none', 'typ' => 'JWT', 'nbf' => 2], 'header', $builder); + $this->assertAttributeEquals(['nbf' => 2], 'claims', $builder); + } + + /** + * @test + * @covers ::__construct + * @covers ::setNotBefore + * @covers ::setRegisteredClaim + * @covers ::set + */ + public function setNotBeforeMustKeepAFluentInterface() + { + $builder = new Builder($this->encoder); + + $this->assertSame($builder, $builder->setNotBefore('2')); + } + + /** + * @test + * @covers ::__construct + * @covers ::setSubject + * @covers ::setRegisteredClaim + * @covers ::set + */ + public function setSubjectMustChangeTheSubClaim() + { + $builder = new Builder($this->encoder); + $builder->setSubject('2'); + + $this->assertAttributeEquals(['alg' => 'none', 'typ' => 'JWT'], 'header', $builder); + $this->assertAttributeEquals(['sub' => '2'], 'claims', $builder); + } + + /** + * @test + * @covers ::__construct + * @covers ::setSubject + * @covers ::setRegisteredClaim + * @covers ::set + */ + public function setSubjectCanReplicateItemOnHeader() + { + $builder = new Builder($this->encoder); + $builder->setSubject('2', true); + + $this->assertAttributeEquals(['alg' => 'none', 'typ' => 'JWT', 'sub' => '2'], 'header', $builder); + $this->assertAttributeEquals(['sub' => '2'], 'claims', $builder); + } + + /** + * @test + * @covers ::__construct + * @covers ::setSubject + * @covers ::setRegisteredClaim + * @covers ::set + */ + public function setSubjectMustKeepAFluentInterface() + { + $builder = new Builder($this->encoder); + + $this->assertSame($builder, $builder->setSubject('2')); + } + + /** + * @test + * @covers ::__construct + * @covers ::set + */ + public function setMustConfigureTheGivenClaim() + { + $builder = new Builder($this->encoder); + $builder->set('userId', 2); + + $this->assertAttributeEquals(['userId' => 2], 'claims', $builder); + } + + /** + * @test + * @covers ::__construct + * @covers ::set + */ + public function setMustKeepAFluentInterface() + { + $builder = new Builder($this->encoder); + + $this->assertSame($builder, $builder->set('userId', 2)); + } + + /** + * @test + * @covers ::__construct + * @covers ::set + * @covers ::getToken + * @covers Lcobucci\JWT\Token::__construct + * @covers Lcobucci\JWT\Token::setEncoder + * @covers Lcobucci\JWT\Token::getHeader + * @covers Lcobucci\JWT\Token::getClaims + * @covers Lcobucci\JWT\Token::getSignature + */ + public function getTokenMustReturnANewTokenWithCurrentConfiguration() + { + $builder = new Builder($this->encoder); + $token = $builder->set('test', 123)->getToken(); + + $this->assertAttributeEquals($token->getHeader(), 'header', $builder); + $this->assertAttributeEquals($token->getClaims(), 'claims', $builder); + $this->assertAttributeSame($token->getSignature(), 'signature', $builder); + } + + /** + * @test + * @covers ::__construct + * @covers ::sign + * @covers ::getToken + * @covers Lcobucci\JWT\Token::__construct + * @covers Lcobucci\JWT\Token::setEncoder + * @covers Lcobucci\JWT\Token::getPayload + */ + public function signMustChangeTheSignature() + { + $signer = $this->getMockBuilder(Signer::class) + ->setMockClassName('SignerMock') + ->getMock(); + + $signature = $this->getMockBuilder(Signature::class) + ->setMockClassName('SignatureMock') + ->disableOriginalConstructor() + ->getMock(); + + $signer->expects($this->any()) + ->method('sign') + ->willReturn($signature); + + $builder = new Builder($this->encoder); + $builder->sign($signer, 'test'); + + $this->assertAttributeSame($signature, 'signature', $builder); + } + + /** + * @test + * @covers ::__construct + * @covers ::sign + * @covers ::getToken + * @covers Lcobucci\JWT\Token::__construct + * @covers Lcobucci\JWT\Token::setEncoder + * @covers Lcobucci\JWT\Token::getPayload + */ + public function signMustKeepAFluentInterface() + { + $signer = $this->getMockBuilder(Signer::class) + ->setMockClassName('SignerMock') + ->getMock(); + + $signature = $this->getMockBuilder(Signature::class) + ->setMockClassName('SignatureMock') + ->disableOriginalConstructor() + ->getMock(); + + $signer->expects($this->any()) + ->method('sign') + ->willReturn($signature); + + $builder = new Builder($this->encoder); + + $this->assertSame($builder, $builder->sign($signer, 'test')); + + return $builder; + } + + /** + * @test + * @depends signMustKeepAFluentInterface + * @covers ::unsign + */ + public function unsignMustRemoveTheSignature(Builder $builder) + { + $builder->unsign(); + + $this->assertAttributeSame(null, 'signature', $builder); + } + + /** + * @test + * @depends signMustKeepAFluentInterface + * @covers ::unsign + */ + public function unsignMustKeepAFluentInterface(Builder $builder) + { + $this->assertSame($builder, $builder->unsign()); + } + + /** + * @test + * @covers ::__construct + * @covers ::set + * @covers ::sign + * @covers ::getToken + * @covers Lcobucci\JWT\Token::__construct + * @covers Lcobucci\JWT\Token::setEncoder + * @covers Lcobucci\JWT\Token::getPayload + * + * @expectedException BadMethodCallException + */ + public function setMustRaiseExceptionWhenTokenHasBeenSigned() + { + $signer = $this->getMockBuilder(Signer::class) + ->setMockClassName('SignerMock') + ->getMock(); + + $signature = $this->getMockBuilder(Signature::class) + ->setMockClassName('SignatureMock') + ->disableOriginalConstructor() + ->getMock(); + + $signer->expects($this->any()) + ->method('sign') + ->willReturn($signature); + + $builder = new Builder($this->encoder); + $builder->sign($signer, 'test'); + $builder->set('test', 123); + } +} diff --git a/test/ParserTest.php b/test/ParserTest.php new file mode 100644 index 00000000..f28f3705 --- /dev/null +++ b/test/ParserTest.php @@ -0,0 +1,261 @@ + + * @since 0.1.0 + * + * @coversDefaultClass Lcobucci\JWT\Parser + */ +class ParserTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var Encoder|\PHPUnit_Framework_MockObject_MockObject + */ + protected $encoder; + + /** + * @var Decoder|\PHPUnit_Framework_MockObject_MockObject + */ + protected $decoder; + + /** + * @var Factory|\PHPUnit_Framework_MockObject_MockObject + */ + protected $factory; + + /** + * {@inheritdoc} + */ + protected function setUp() + { + $this->encoder = $this->getMockBuilder(Encoder::class) + ->setMockClassName('EncoderMock') + ->getMock(); + + $this->decoder = $this->getMockBuilder(Decoder::class) + ->setMockClassName('DecoderMock') + ->getMock(); + + $this->factory = $this->getMockBuilder(Factory::class) + ->setMockClassName('FactoryMock') + ->getMock(); + } + + /** + * @test + * @covers ::__construct + */ + public function constructMustConfigureTheAttributes() + { + $parser = new Parser($this->encoder, $this->decoder, $this->factory); + + $this->assertAttributeSame($this->encoder, 'encoder', $parser); + $this->assertAttributeSame($this->decoder, 'decoder', $parser); + $this->assertAttributeSame($this->factory, 'factory', $parser); + } + + /** + * @test + * @covers ::__construct + * @covers ::parse + * @covers ::splitJwt + * + * @expectedException InvalidArgumentException + */ + public function parseMustRaiseExceptionWhenJWSIsNotAString() + { + $parser = new Parser($this->encoder, $this->decoder, $this->factory); + + $parser->parse(['asdasd']); + } + + /** + * @test + * @covers ::__construct + * @covers ::parse + * @covers ::splitJwt + * + * @expectedException InvalidArgumentException + */ + public function parseMustRaiseExceptionWhenJWSDontHaveThreeParts() + { + $parser = new Parser($this->encoder, $this->decoder, $this->factory); + + $parser->parse(''); + } + + /** + * @test + * @covers ::__construct + * @covers ::parse + * @covers ::splitJwt + * @covers ::parseHeader + * + * @expectedException RuntimeException + */ + public function parseMustRaiseExceptionWhenHeaderCannotBeDecoded() + { + $this->decoder->expects($this->any()) + ->method('jsonDecode') + ->willThrowException(new RuntimeException()); + + $parser = new Parser($this->encoder, $this->decoder, $this->factory); + + $parser->parse('asdfad.asdfasdf.'); + } + + /** + * @test + * @covers ::__construct + * @covers ::parse + * @covers ::splitJwt + * @covers ::parseHeader + * + * @expectedException InvalidArgumentException + */ + public function parseMustRaiseExceptionWhenHeaderIsNotAnArray() + { + $this->decoder->expects($this->any()) + ->method('jsonDecode') + ->willReturn('asdfasdfasd'); + + $parser = new Parser($this->encoder, $this->decoder, $this->factory); + + $parser->parse('a.a.'); + } + + /** + * @test + * @covers ::__construct + * @covers ::parse + * @covers ::splitJwt + * @covers ::parseHeader + * + * @expectedException InvalidArgumentException + */ + public function parseMustRaiseExceptionWhenHeaderIsFromAnEncryptedToken() + { + $this->decoder->expects($this->any()) + ->method('jsonDecode') + ->willReturn(['enc' => 'AAA']); + + $parser = new Parser($this->encoder, $this->decoder, $this->factory); + + $parser->parse('a.a.'); + } + + /** + * @test + * @covers ::__construct + * @covers ::parse + * @covers ::splitJwt + * @covers ::parseHeader + * @covers ::parseClaims + * + * @expectedException InvalidArgumentException + */ + public function parseMustRaiseExceptionWhenClaimSetIsNotAnArray() + { + $this->decoder->expects($this->at(1)) + ->method('jsonDecode') + ->willReturn(['typ' => 'JWT', 'alg' => 'none']); + + $this->decoder->expects($this->at(3)) + ->method('jsonDecode') + ->willReturn('asdfasdfasd'); + + $parser = new Parser($this->encoder, $this->decoder, $this->factory); + + $parser->parse('a.a.'); + } + + /** + * @test + * @covers ::__construct + * @covers ::parse + * @covers ::splitJwt + * @covers ::parseHeader + * @covers ::parseClaims + * @covers ::parseSignature + * @covers Lcobucci\JWT\Token::__construct + * @covers Lcobucci\JWT\Token::setEncoder + */ + public function parseMustReturnANonSignedTokenWhenSignatureIsNotInformed() + { + $this->decoder->expects($this->at(1)) + ->method('jsonDecode') + ->willReturn(['typ' => 'JWT', 'alg' => 'none']); + + $this->decoder->expects($this->at(3)) + ->method('jsonDecode') + ->willReturn(['aud' => 'test']); + + $parser = new Parser($this->encoder, $this->decoder, $this->factory); + + $token = $parser->parse('a.a.'); + + $this->assertAttributeEquals(['typ' => 'JWT', 'alg' => 'none'], 'header', $token); + $this->assertAttributeEquals(['aud' => 'test'], 'claims', $token); + $this->assertAttributeEquals(null, 'signature', $token); + $this->assertAttributeSame($this->encoder, 'encoder', $token); + } + + /** + * @test + * @covers ::__construct + * @covers ::parse + * @covers ::splitJwt + * @covers ::parseHeader + * @covers ::parseClaims + * @covers ::parseSignature + * @covers Lcobucci\JWT\Token::__construct + * @covers Lcobucci\JWT\Token::setEncoder + * @covers Lcobucci\JWT\Signature::__construct + */ + public function parseMustReturnASignedTokenWhenSignatureIsInformed() + { + $signer = $this->getMockBuilder(Signer::class) + ->setMockClassName('SignerMock') + ->getMock(); + + $this->decoder->expects($this->at(1)) + ->method('jsonDecode') + ->willReturn(['typ' => 'JWT', 'alg' => 'HS256']); + + $this->decoder->expects($this->at(3)) + ->method('jsonDecode') + ->willReturn(['aud' => 'test']); + + $this->decoder->expects($this->at(4)) + ->method('base64UrlDecode') + ->willReturn('aaa'); + + $this->factory->expects($this->any()) + ->method('create') + ->willReturn($signer); + + $parser = new Parser($this->encoder, $this->decoder, $this->factory); + + $token = $parser->parse('a.a.a'); + + $this->assertAttributeEquals(['typ' => 'JWT', 'alg' => 'HS256'], 'header', $token); + $this->assertAttributeEquals(['aud' => 'test'], 'claims', $token); + $this->assertAttributeEquals(new Signature($signer, 'aaa'), 'signature', $token); + $this->assertAttributeSame($this->encoder, 'encoder', $token); + } +} diff --git a/test/Parsing/DecoderTest.php b/test/Parsing/DecoderTest.php new file mode 100644 index 00000000..8cf0d513 --- /dev/null +++ b/test/Parsing/DecoderTest.php @@ -0,0 +1,54 @@ + + * @since 0.1.0 + * + * @coversDefaultClass Lcobucci\JWT\Parsing\Decoder + */ +class DecoderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @test + * @covers ::jsonDecode + */ + public function jsonDecodeMustReturnTheDecodedData() + { + $decoder = new Decoder(); + + $this->assertEquals(['test' => 'test'], $decoder->jsonDecode('{"test":"test"}')); + } + + /** + * @test + * @covers ::jsonDecode + * + * @expectedException \RuntimeException + */ + public function jsonDecodeMustRaiseExceptionWhenAnErrorHasOccured() + { + $decoder = new Decoder(); + $decoder->jsonDecode('{"test":\'test\'}'); + } + + /** + * @test + * @covers ::base64UrlDecode + */ + public function base64UrlDecodeMustReturnTheRightData() + { + $data = base64_decode('0MB2wKB+L3yvIdzeggmJ+5WOSLaRLTUPXbpzqUe0yuo='); + + $decoder = new Decoder(); + $this->assertEquals($data, $decoder->base64UrlDecode('0MB2wKB-L3yvIdzeggmJ-5WOSLaRLTUPXbpzqUe0yuo')); + } +} diff --git a/test/Parsing/EncoderTest.php b/test/Parsing/EncoderTest.php new file mode 100644 index 00000000..f0cf6cb2 --- /dev/null +++ b/test/Parsing/EncoderTest.php @@ -0,0 +1,54 @@ + + * @since 0.1.0 + * + * @coversDefaultClass Lcobucci\JWT\Parsing\Encoder + */ +class EncoderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @test + * @covers ::jsonEncode + */ + public function jsonEncodeMustReturnAJSONString() + { + $encoder = new Encoder(); + + $this->assertEquals('{"test":"test"}', $encoder->jsonEncode(['test' => 'test'])); + } + + /** + * @test + * @covers ::jsonEncode + * + * @expectedException \RuntimeException + */ + public function jsonEncodeMustRaiseExceptionWhenAnErrorHasOccured() + { + $encoder = new Encoder(); + $encoder->jsonEncode("\xB1\x31"); + } + + /** + * @test + * @covers ::base64UrlEncode + */ + public function base64UrlEncodeMustReturnAnUrlSafeBase64() + { + $data = base64_decode('0MB2wKB+L3yvIdzeggmJ+5WOSLaRLTUPXbpzqUe0yuo='); + + $encoder = new Encoder(); + $this->assertEquals('0MB2wKB-L3yvIdzeggmJ-5WOSLaRLTUPXbpzqUe0yuo', $encoder->base64UrlEncode($data)); + } +} diff --git a/test/SignatureTest.php b/test/SignatureTest.php new file mode 100644 index 00000000..dbef9e4e --- /dev/null +++ b/test/SignatureTest.php @@ -0,0 +1,93 @@ + + * @since 0.1.0 + * + * @coversDefaultClass Lcobucci\JWT\Signature + */ +class SignatureTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var Signer|\PHPUnit_Framework_MockObject_MockObject + */ + protected $signer; + + /** + * {@inheritdoc} + */ + protected function setUp() + { + $this->signer = $this->getMockBuilder(Signer::class) + ->setMockClassName('SignerMock') + ->getMock(); + } + + /** + * @test + * @covers ::__construct + */ + public function constructorMustConfigureAttributes() + { + $signature = new Signature($this->signer, 'test'); + + $this->assertAttributeSame($this->signer, 'signer', $signature); + $this->assertAttributeEquals('test', 'hash', $signature); + } + + /** + * @test + * @covers ::__construct + * @covers ::__toString + */ + public function toStringMustReturnTheHash() + { + $signature = new Signature($this->signer, 'test'); + + $this->assertEquals('test', (string) $signature); + } + + /** + * @test + * @covers ::__construct + * @covers ::__toString + * @covers ::verify + */ + public function verifyMustReturnTrueWhenHashMatches() + { + $this->signer->expects($this->any()) + ->method('createHash') + ->willReturn('test'); + + $signature = new Signature($this->signer, 'test'); + + $this->assertTrue($signature->verify('one', 'key')); + } + + /** + * @test + * @covers ::__construct + * @covers ::__toString + * @covers ::verify + */ + public function verifyMustReturnFalseWhenHashDoesNotMatch() + { + $this->signer->expects($this->any()) + ->method('createHash') + ->willReturn('testing'); + + $signature = new Signature($this->signer, 'test'); + + $this->assertFalse($signature->verify('one', 'key')); + } +} diff --git a/test/Signer/BaseSignerTest.php b/test/Signer/BaseSignerTest.php new file mode 100644 index 00000000..3dc32f68 --- /dev/null +++ b/test/Signer/BaseSignerTest.php @@ -0,0 +1,70 @@ + + * @since 0.1.0 + * + * @coversDefaultClass Lcobucci\JWT\Signer\BaseSigner + */ +class BaseSignerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var BaseSigner|\PHPUnit_Framework_MockObject_MockObject + */ + protected $signer; + + /** + * {@inheritdoc} + */ + protected function setUp() + { + $this->signer = $this->getMockBuilder(BaseSigner::class) + ->setMockClassName('BaseSignerMock') + ->getMockForAbstractClass(); + + $this->signer->expects($this->any()) + ->method('getAlgorithmId') + ->willReturn('TEST123'); + + $this->signer->expects($this->any()) + ->method('createHash') + ->willReturn('test'); + } + + /** + * @test + * @covers ::modifyHeader + */ + public function modifyHeaderShouldChangeAlgorithmAndType() + { + $headers = []; + + $this->signer->modifyHeader($headers); + + $this->assertEquals($headers['typ'], 'JWS'); + $this->assertEquals($headers['alg'], 'TEST123'); + } + + /** + * @test + * @covers ::sign + * @covers Lcobucci\JWT\Signature::__construct + */ + public function signMustReturnANewSignature() + { + $this->assertEquals( + new Signature($this->signer, 'test'), + $this->signer->sign('test', '123') + ); + } +} diff --git a/test/Signer/FactoryTest.php b/test/Signer/FactoryTest.php new file mode 100644 index 00000000..af3bf553 --- /dev/null +++ b/test/Signer/FactoryTest.php @@ -0,0 +1,67 @@ + + * @since 0.1.0 + * + * @coversDefaultClass Lcobucci\JWT\Signer\Factory + */ +class FactoryTest extends \PHPUnit_Framework_TestCase +{ + /** + * @test + * @covers ::create + */ + public function createMustBeAbleReturnASha256Signer() + { + $factory = new Factory(); + + $this->assertInstanceOf(Sha256::class, $factory->create('HS256')); + } + + /** + * @test + * @covers ::create + */ + public function createMustBeAbleReturnASha384Signer() + { + $factory = new Factory(); + + $this->assertInstanceOf(Sha384::class, $factory->create('HS384')); + } + + /** + * @test + * @covers ::create + */ + public function createMustBeAbleReturnASha512Signer() + { + $factory = new Factory(); + + $this->assertInstanceOf(Sha512::class, $factory->create('HS512')); + } + + /** + * @test + * @covers ::create + * + * @expectedException InvalidArgumentException + */ + public function createMustRaiseExceptionWhenIdIsInvalid() + { + $factory = new Factory(); + $factory->create('testing'); + } +} diff --git a/test/Signer/HmacTest.php b/test/Signer/HmacTest.php new file mode 100644 index 00000000..0ff43dc7 --- /dev/null +++ b/test/Signer/HmacTest.php @@ -0,0 +1,54 @@ + + * @since 0.1.0 + * + * @coversDefaultClass Lcobucci\JWT\Signer\Hmac + */ +class HmacTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var Hmac|\PHPUnit_Framework_MockObject_MockObject + */ + protected $signer; + + /** + * {@inheritdoc} + */ + protected function setUp() + { + $this->signer = $this->getMockBuilder(Hmac::class) + ->setMockClassName('HmacMock') + ->getMockForAbstractClass(); + + $this->signer->expects($this->any()) + ->method('getAlgorithmId') + ->willReturn('TEST123'); + + $this->signer->expects($this->any()) + ->method('getAlgorithm') + ->willReturn('sha256'); + } + + /** + * @test + * @covers ::createHash + */ + public function createHashMustReturnAHashAccordingWithTheAlgorithm() + { + $this->assertEquals( + hash_hmac('sha256', 'test', '123', true), + $this->signer->createHash('test', '123') + ); + } +} diff --git a/test/Signer/Sha256Test.php b/test/Signer/Sha256Test.php new file mode 100644 index 00000000..6fdeeb16 --- /dev/null +++ b/test/Signer/Sha256Test.php @@ -0,0 +1,41 @@ + + * @since 0.1.0 + * + * @coversDefaultClass Lcobucci\JWT\Signer\Sha256 + */ +class Sha256Test extends \PHPUnit_Framework_TestCase +{ + /** + * @test + * @covers ::getAlgorithmId + */ + public function getAlgorithmIdMustBeCorrect() + { + $signer = new Sha256(); + + $this->assertEquals('HS256', $signer->getAlgorithmId()); + } + + /** + * @test + * @covers ::getAlgorithm + */ + public function getAlgorithmMustBeCorrect() + { + $signer = new Sha256(); + + $this->assertEquals('sha256', $signer->getAlgorithm()); + } +} diff --git a/test/Signer/Sha384Test.php b/test/Signer/Sha384Test.php new file mode 100644 index 00000000..9eddd083 --- /dev/null +++ b/test/Signer/Sha384Test.php @@ -0,0 +1,41 @@ + + * @since 0.1.0 + * + * @coversDefaultClass Lcobucci\JWT\Signer\Sha384 + */ +class Sha384Test extends \PHPUnit_Framework_TestCase +{ + /** + * @test + * @covers ::getAlgorithmId + */ + public function getAlgorithmIdMustBeCorrect() + { + $signer = new Sha384(); + + $this->assertEquals('HS384', $signer->getAlgorithmId()); + } + + /** + * @test + * @covers ::getAlgorithm + */ + public function getAlgorithmMustBeCorrect() + { + $signer = new Sha384(); + + $this->assertEquals('sha384', $signer->getAlgorithm()); + } +} diff --git a/test/Signer/Sha512Test.php b/test/Signer/Sha512Test.php new file mode 100644 index 00000000..571f2193 --- /dev/null +++ b/test/Signer/Sha512Test.php @@ -0,0 +1,41 @@ + + * @since 0.1.0 + * + * @coversDefaultClass Lcobucci\JWT\Signer\Sha512 + */ +class Sha512Test extends \PHPUnit_Framework_TestCase +{ + /** + * @test + * @covers ::getAlgorithmId + */ + public function getAlgorithmIdMustBeCorrect() + { + $signer = new Sha512(); + + $this->assertEquals('HS512', $signer->getAlgorithmId()); + } + + /** + * @test + * @covers ::getAlgorithm + */ + public function getAlgorithmMustBeCorrect() + { + $signer = new Sha512(); + + $this->assertEquals('sha512', $signer->getAlgorithm()); + } +} diff --git a/test/TokenTest.php b/test/TokenTest.php new file mode 100644 index 00000000..ca51b4d6 --- /dev/null +++ b/test/TokenTest.php @@ -0,0 +1,230 @@ + + * @since 0.1.0 + * + * @coversDefaultClass Lcobucci\JWT\Token + */ +class TokenTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var Encoder|\PHPUnit_Framework_MockObject_MockObject + */ + protected $encoder; + + /** + * {@inheritdoc} + */ + protected function setUp() + { + $this->encoder = $this->getMockBuilder(Encoder::class) + ->setMockClassName('EncoderMock') + ->getMock(); + } + + /** + * @test + * @covers ::__construct + */ + public function constructMustInitializeAnEmptyPlainTextTokenWhenNoArgumentsArePassed() + { + $token = new Token(); + + $this->assertAttributeEquals(['alg' => 'none'], 'header', $token); + $this->assertAttributeEquals([], 'claims', $token); + $this->assertAttributeEquals(null, 'signature', $token); + } + + /** + * @test + * @covers ::__construct + * @covers ::setEncoder + */ + public function setEncoderMustConfigureTheEncoderAttribute() + { + $token = new Token(); + $token->setEncoder($this->encoder); + + $this->assertAttributeSame($this->encoder, 'encoder', $token); + } + + /** + * @test + * @covers ::__construct + * @covers ::getHeader + */ + public function getHeaderMustReturnTheConfiguredHeader() + { + $token = new Token(['test' => 'testing']); + + $this->assertEquals(['test' => 'testing'], $token->getHeader()); + } + + /** + * @test + * @covers ::__construct + * @covers ::getClaims + */ + public function getClaimsMustReturnTheConfiguredClaims() + { + $token = new Token([], ['test' => 'testing']); + + $this->assertEquals(['test' => 'testing'], $token->getClaims()); + } + + /** + * @test + * @covers ::__construct + * @covers ::getSignature + */ + public function getSignatureMustReturnTheConfiguredSignature() + { + $signature = $this->getMockBuilder(Signature::class) + ->setMockClassName('SignatureMock') + ->disableOriginalConstructor() + ->getMock(); + + $token = new Token([], [], $signature); + + $this->assertSame($signature, $token->getSignature()); + } + + /** + * @test + * @covers ::__construct + * @covers ::verify + * + * @expectedException BadMethodCallException + */ + public function verifyMustRaiseExceptionWhenTokenIsUnsigned() + { + $token = new Token(); + $token->verify('test'); + } + + /** + * @test + * @covers ::__construct + * @covers ::setEncoder + * @covers ::getPayload + * @covers ::verify + */ + public function verifyMustDelegateTheValidationToSignature() + { + $signature = $this->getMockBuilder(Signature::class) + ->setMockClassName('SignatureMock') + ->disableOriginalConstructor() + ->getMock(); + + $signature->expects($this->once()) + ->method('verify') + ->willReturn(true); + + $token = new Token([], [], $signature); + $token->setEncoder($this->encoder); + + $this->assertTrue($token->verify('test')); + } + + /** + * @test + * @covers ::__construct + * @covers ::getPayload + * @covers ::__toString + */ + public function toStringMustReturnAnEmptyStringWhenEncoderIsNotDefined() + { + $token = new Token(); + + $this->assertEmpty((string) $token); + } + + /** + * @test + * @covers ::__construct + * @covers ::setEncoder + * @covers ::getPayload + * @covers ::__toString + */ + public function toStringMustReturnEncodedDataWithEmptySignature() + { + $token = new Token(); + $token->setEncoder($this->encoder); + + $this->createMockExpectations(); + + $this->assertEquals('test.test.', (string) $token); + } + + /** + * @test + * @covers ::__construct + * @covers ::setEncoder + * @covers ::getPayload + * @covers ::__toString + */ + public function toStringMustReturnEncodedData() + { + $signature = $this->getMockBuilder(Signature::class) + ->setMockClassName('SignatureMock') + ->disableOriginalConstructor() + ->getMock(); + + $signature->expects($this->any()) + ->method('__toString') + ->willReturn('test'); + + $token = new Token(['alg' => 'none'], [], $signature); + $token->setEncoder($this->encoder); + + $this->createMockExpectations('test'); + + $this->assertEquals('test.test.test', (string) $token); + } + + /** + * Fill the mock expectations + */ + protected function createMockExpectations($signature = null) + { + $this->encoder->expects($this->at(0)) + ->method('jsonEncode') + ->with(['alg' => 'none']) + ->willReturn('test'); + + $this->encoder->expects($this->at(1)) + ->method('base64UrlEncode') + ->with('test') + ->willReturn('test'); + + $this->encoder->expects($this->at(2)) + ->method('jsonEncode') + ->with([]) + ->willReturn('test'); + + $this->encoder->expects($this->at(3)) + ->method('base64UrlEncode') + ->with('test') + ->willReturn('test'); + + if ($signature) { + $this->encoder->expects($this->at(4)) + ->method('base64UrlEncode') + ->with('test') + ->willReturn('test'); + } + } + +}