From 11ffbd7aa9ea09c930464e84e76e1a388de53365 Mon Sep 17 00:00:00 2001 From: Florent Morselli Date: Mon, 21 Sep 2015 13:17:45 +0200 Subject: [PATCH] Prepared for v6.0.0 --- .coveralls.yml | 1 + .scrutinizer.yml | 5 +- .travis.yml | 9 +- README.md | 24 +-- composer.json | 7 +- doc/Extend.md | 331 ------------------------------------------ examples/HOTP.php | 33 ----- examples/OTP.php | 109 -------------- examples/TOTP.php | 26 ---- examples/examples.php | 45 ------ lib/HOTP.php | 33 ++++- lib/HOTPInterface.php | 9 ++ lib/OTP.php | 147 +++++++++++++++++++ lib/OTPInterface.php | 65 +++++++++ lib/TOTP.php | 23 ++- lib/TOTPInterface.php | 9 ++ phpunit.xml.dist | 1 - tests/HOTPTest.php | 135 +++-------------- tests/OTPTest.php | 92 ------------ tests/TOTPTest.php | 59 +++----- 20 files changed, 331 insertions(+), 832 deletions(-) create mode 100644 .coveralls.yml delete mode 100644 doc/Extend.md delete mode 100644 examples/HOTP.php delete mode 100644 examples/OTP.php delete mode 100644 examples/TOTP.php delete mode 100644 examples/examples.php delete mode 100644 tests/OTPTest.php diff --git a/.coveralls.yml b/.coveralls.yml new file mode 100644 index 00000000..6b74c218 --- /dev/null +++ b/.coveralls.yml @@ -0,0 +1 @@ +src_dir: lib diff --git a/.scrutinizer.yml b/.scrutinizer.yml index db088353..19625d7c 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -2,7 +2,7 @@ before_commands: - "composer install --prefer-dist" tools: - external_code_coverage: true + external_code_coverage: false php_mess_detector: true php_code_sniffer: true php_analyzer: true @@ -11,8 +11,5 @@ tools: php_cpd: true php_pdepend: excluded_dirs: [vendor/*, doc/*, tests/*] - external_code_coverage: - timeout: 7200 - runs: 5 filter: excluded_paths: [vendor/*, doc/*, tests/*] diff --git a/.travis.yml b/.travis.yml index de5f8421..246fcfc6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,19 +1,16 @@ language: php php: - - 5.3 - - 5.4 - - 5.5 - 5.6 - 7 - hhvm before_script: - composer install --no-interaction + - mkdir -p build/logs script: - - php vendor/bin/phpunit --coverage-clover ./clover.xml + - vendor/bin/phpunit --coverage-clover build/logs/clover.xml after_success: - - wget https://scrutinizer-ci.com/ocular.phar - - php ocular.phar code-coverage:upload --format=php-clover ./clover.xml + - vendor/bin/coveralls --no-interaction diff --git a/README.md b/README.md index 2ed93884..a98b64d7 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # TOTP / HOTP library in PHP # [![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/Spomky-Labs/otphp/badges/quality-score.png?s=a184d180414b30764d19b421a12d6cf7e9e5e7c2)](https://scrutinizer-ci.com/g/Spomky-Labs/otphp/) -[![Code Coverage](https://scrutinizer-ci.com/g/Spomky-Labs/otphp/badges/coverage.png?s=d1bd1b26b56e581d6a4d1deb87eaadc51a05f31d)](https://scrutinizer-ci.com/g/Spomky-Labs/otphp/) +[![Coverage Status](https://coveralls.io/repos/Spomky-Labs/otphp/badge.svg?branch=master&service=github)](https://coveralls.io/github/Spomky-Labs/otphp?branch=master) [![Build Status](https://travis-ci.org/Spomky-Labs/otphp.svg?branch=master)](https://travis-ci.org/Spomky-Labs/otphp) [![HHVM Status](http://hhvm.h4cc.de/badge/Spomky-Labs/otphp.png)](http://hhvm.h4cc.de/package/Spomky-Labs/otphp) @@ -15,39 +15,23 @@ A php library for generating one time passwords according to [ RFC 4226 ](http:/ This library is compatible with Google Authenticator apps available for Android and iPhone. It is also compatible with other applications such as [FreeOTP](https://play.google.com/store/apps/details?id=org.fedorahosted.freeotp) for example. -This is a fork of https://github.com/lelag/otphp with the following changes: - -* Continuous unit and functional tests using Travis-CI -* 100% test coverage -* Code Quality improvement -* Better namespace usage -* Issuer support -* Window support -* Completely abstract objects - ## The Release Process The release process [is described here](doc/Release.md). ## Prerequisites -This library needs at least `PHP 5.3`. -It has been successfully tested using `PHP 5.3` to `PHP 5.6`, `PHP 7` and `HHVM` +This library needs at least `PHP 5.6`. +It has been successfully tested using `PHP 5.6`, `PHP 7` and `HHVM` ## Installation The preferred way to install this library is to rely on Composer: ```sh -composer require "spomky-labs/otphp" "~5.0.0" +composer require "spomky-labs/otphp" "~6.0.0" ``` -## Extend the library - -This library only contains the logic. You must extend all classes to define setters and getters. - -Look at [Extend classes](doc/Extend.md) for more information and examples. - ## How to use Your classes are ready to use? Have a look at [How to use](doc/Use.md) to generate your first OTP. diff --git a/composer.json b/composer.json index 9c143478..b138958b 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ } ], "require": { - "php": ">=5.3.2", + "php": ">=5.6", "christian-riesen/base32": "~1.1" }, "require-dev": { @@ -27,12 +27,9 @@ "autoload": { "psr-4": { "OTPHP\\": "lib/" } }, - "autoload-dev": { - "psr-4": { "MyProject\\": "examples/" } - }, "extra": { "branch-alias": { - "dev-master": "5.0.x-dev" + "dev-master": "6.0.x-dev" } } } diff --git a/doc/Extend.md b/doc/Extend.md deleted file mode 100644 index d3c55405..00000000 --- a/doc/Extend.md +++ /dev/null @@ -1,331 +0,0 @@ -# Extending all classes - -## Description of classes - -This library has three abstract classes: - -* OTP: the base of all OTP classes -* TOTP: Time Based OTP -* HOTP: Counter Based OTP - -## OTP Class - -The all classes must implement the following methods: - -* ```public functon getLabel();``` -* ```public functon getIssuer();``` -* ```public functon isIssuerIncludedAsParameter();``` -* ```public functon getDigits();``` -* ```public functon getDigest();``` - -If you wand to extend - -* TOTP Class, you must also implement ```public functon getInterval();```. -* HOTP Class, you must also implement ```public functon getCounter();``` and ```protected functon updateCounter($counter);```. - -## Implementation - -### TOTP Class - -The following class is a possible implementation of the TOTP Class: - -```php -secret = $secret; - return $this; - } - - public function getSecret() - { - return $this->secret; - } - - public function setLabel($label) - { - if ($this->hasSemicolon($label)) { - throw new \Exception("Label must not contain a semi-colon."); - } - $this->label = $label; - return $this; - } - - public function getLabel() - { - return $this->label; - } - - public function setIssuer($issuer) - { - if ($this->hasSemicolon($issuer)) { - throw new \Exception("Issuer must not contain a semi-colon."); - } - $this->issuer = $issuer; - return $this; - } - - public function getIssuer() - { - return $this->issuer; - } - - public function isIssuerIncludedAsParameter() - { - return $this->issuer_included_as_parameter; - } - - public function setIssuerIncludedAsParameter($issuer_included_as_parameter) - { - $this->issuer_included_as_parameter = $issuer_included_as_parameter; - return $this; - } - - public function setDigits($digits) - { - if( !is_numeric($digits) || $digits < 1 ) { - throw new \Exception("Digits must be at least 1."); - } - $this->digits = $digits; - return $this; - } - - public function getDigits() - { - return $this->digits; - } - - public function setDigest($digest) - { - if( !in_array($digest, array('md5', 'sha1', 'sha256', 'sha512')) ) { - throw new \Exception("'$digest' digest is not supported."); - } - $this->digest = $digest; - return $this; - } - - public function getDigest() - { - return $this->digest; - } - - public function setInterval($interval) - { - if( !is_integer($interval) || $interval < 1 ) { - throw new \Exception("Interval must be at least 1."); - } - $this->interval = $interval; - return $this; - } - - public function getInterval() - { - return $this->interval; - } - - private function hasSemicolon($value) - { - $semicolons = array(':', '%3A', '%3a'); - foreach ($semicolons as $semicolon) { - if (false !== strpos($value, $semicolon)) { - return true; - } - } - - return false; - } -} -``` - -This this class, you can easily create a TOTP object: - -```php -setSecret('JDDK4U6G3BJLEZ7Y'); -``` - -Your object is ready to use. -You can also change all options: - -```php -$totp->setLabel('foo@bar.baz') - ->setIssuer('My Project') - ->setDigits(4) - ->setDigest('sha512') - ->setInterval(60); -``` - - -### HOTP Class - -The following class is a possible implementation of the HOTP Class: - -```php -secret = $secret; - return $this; - } - - public function getSecret() - { - return $this->secret; - } - - public function setLabel($label) - { - if ($this->hasSemicolon($label)) { - throw new \Exception("Label must not contain a semi-colon."); - } - $this->label = $label; - return $this; - } - - public function getLabel() - { - return $this->label; - } - - public function setIssuer($issuer) - { - if ($this->hasSemicolon($issuer)) { - throw new \Exception("Issuer must not contain a semi-colon."); - } - $this->issuer = $issuer; - return $this; - } - - public function getIssuer() - { - return $this->issuer; - } - - public function isIssuerIncludedAsParameter() - { - return $this->issuer_included_as_parameter; - } - - public function setIssuerIncludedAsParameter($issuer_included_as_parameter) - { - $this->issuer_included_as_parameter = $issuer_included_as_parameter; - return $this; - } - - public function setDigits($digits) - { - if( !is_numeric($digits) || $digits < 1 ) { - throw new \Exception("Digits must be at least 1."); - } - $this->digits = $digits; - return $this; - } - - public function getDigits() - { - return $this->digits; - } - - public function setDigest($digest) - { - if( !in_array($digest, array('md5', 'sha1', 'sha256', 'sha512')) ) { - throw new \Exception("'$digest' digest is not supported."); - } - $this->digest = $digest; - return $this; - } - - public function getDigest() - { - return $this->digest; - } - - public function setCounter($counter) - { - if( !is_integer($counter) || $counter < 0 ) { - throw new \Exception("Counter must be at least 0."); - } - $this->counter = $counter; - return $this; - } - - public function getCounter() - { - return $this->counter; - } - - public function updateCounter($counter) - { - $this->counter = $counter; - return $this; - } - - private function hasSemicolon($value) - { - $semicolons = array(':', '%3A', '%3a'); - foreach ($semicolons as $semicolon) { - if (false !== strpos($value, $semicolon)) { - return true; - } - } - - return false; - } -} -``` - -This this class, you can easily create a HOTP object: - -```php -setSecret('JDDK4U6G3BJLEZ7Y'); -``` - -Your object is ready to use. -You can also change all options: - -```php -$hotp->setLabel('foo@bar.baz') - ->setIssuer('My Project') - ->setDigits(4) - ->setDigest('sha512') - ->setCounter(100); -``` diff --git a/examples/HOTP.php b/examples/HOTP.php deleted file mode 100644 index 0298b159..00000000 --- a/examples/HOTP.php +++ /dev/null @@ -1,33 +0,0 @@ -counter = $counter; - - return $this; - } - - public function getCounter() - { - return $this->counter; - } - - public function updateCounter($counter) - { - $this->counter = $counter; - - return $this; - } -} diff --git a/examples/OTP.php b/examples/OTP.php deleted file mode 100644 index a7649043..00000000 --- a/examples/OTP.php +++ /dev/null @@ -1,109 +0,0 @@ -secret = $secret; - - return $this; - } - - public function getSecret() - { - return $this->secret; - } - - public function setLabel($label) - { - if ($this->hasSemicolon($label)) { - throw new \Exception('Label must not contain a semi-colon.'); - } - $this->label = $label; - - return $this; - } - - public function getLabel() - { - return $this->label; - } - - public function setIssuer($issuer) - { - if ($this->hasSemicolon($issuer)) { - throw new \Exception('Issuer must not contain a semi-colon.'); - } - $this->issuer = $issuer; - - return $this; - } - - public function getIssuer() - { - return $this->issuer; - } - - public function isIssuerIncludedAsParameter() - { - return $this->issuer_included_as_parameter; - } - - public function setIssuerIncludedAsParameter($issuer_included_as_parameter) - { - $this->issuer_included_as_parameter = $issuer_included_as_parameter; - - return $this; - } - - public function setDigits($digits) - { - if (!is_numeric($digits) || $digits < 1) { - throw new \Exception('Digits must be at least 1.'); - } - $this->digits = $digits; - - return $this; - } - - public function getDigits() - { - return $this->digits; - } - - public function setDigest($digest) - { - if (!in_array($digest, ['md5', 'sha1', 'sha256', 'sha512'])) { - throw new \Exception("'$digest' digest is not supported."); - } - $this->digest = $digest; - - return $this; - } - - public function getDigest() - { - return $this->digest; - } - - private function hasSemicolon($value) - { - $semicolons = [':', '%3A', '%3a']; - foreach ($semicolons as $semicolon) { - if (false !== strpos($value, $semicolon)) { - return true; - } - } - - return false; - } -} diff --git a/examples/TOTP.php b/examples/TOTP.php deleted file mode 100644 index 49002295..00000000 --- a/examples/TOTP.php +++ /dev/null @@ -1,26 +0,0 @@ -interval = $interval; - - return $this; - } - - public function getInterval() - { - return $this->interval; - } -} diff --git a/examples/examples.php b/examples/examples.php deleted file mode 100644 index 63ce66d9..00000000 --- a/examples/examples.php +++ /dev/null @@ -1,45 +0,0 @@ -setSecret($secret) - ->setDigest('sha256') - ->setDigits(8) - ->setLabel('alice@localhost') - ->setIssuer('MyProject') - ->setInterval(60) - ->setIssuerIncludedAsParameter(true); - -$hotp = new HOTP(); - -$hotp->setSecret($secret) - ->setDigest('sha512') - ->setDigits(5) - ->setLabel('alice@localhost') - ->setIssuer('MyProject') - ->setCounter(1000) - ->setIssuerIncludedAsParameter(true); - -printf(str_repeat('=', 50)."\r\n"); -printf("Time Based On-time password\r\n"); -printf(str_repeat('=', 50)."\r\n"); -printf("Secret is %s\r\n", $secret); -printf("TOTP provisioning URI is %s\r\n", $totp->getProvisioningUri()); -printf("Current code is %s\r\n", $totp->now()); - -printf(str_repeat("\r\n", 3)); - -printf(str_repeat('=', 50)."\r\n"); -printf("Counter Based On-time password\r\n"); -printf(str_repeat('=', 50)."\r\n"); -printf("Secret is %s\r\n", $secret); -printf("TOTP provisioning URI is %s\r\n", $hotp->getProvisioningUri()); -printf("Current code is %s\r\n", $hotp->at(1010)); diff --git a/lib/HOTP.php b/lib/HOTP.php index a70e4b9c..64e25eab 100644 --- a/lib/HOTP.php +++ b/lib/HOTP.php @@ -2,20 +2,43 @@ namespace OTPHP; -abstract class HOTP extends OTP implements HOTPInterface +class HOTP extends OTP implements HOTPInterface { /** * {@inheritdoc} */ - public function getProvisioningUri($google_compatible = true) + public function setCounter($counter) { - return $this->generateURI('hotp', ['counter' => $this->getCounter()], $google_compatible); + if (!is_int($counter) || $counter < 0) { + throw new \InvalidArgumentException('Counter must be at least 0.'); + } + + return $this->setParameter('counter', $counter); } /** - * @param int $counter The new initial counter (a positive integer) + * {@inheritdoc} + */ + public function getCounter() + { + return $this->getParameter('counter'); + } + + /** + * {@inheritdoc} */ - abstract protected function updateCounter($counter); + private function updateCounter($counter) + { + return $this->setCounter($counter); + } + + /** + * {@inheritdoc} + */ + public function getProvisioningUri($google_compatible = true) + { + return $this->generateURI('hotp', ['counter' => $this->getCounter()], $google_compatible); + } /** * {@inheritdoc} diff --git a/lib/HOTPInterface.php b/lib/HOTPInterface.php index 84395783..32357ea1 100644 --- a/lib/HOTPInterface.php +++ b/lib/HOTPInterface.php @@ -8,4 +8,13 @@ interface HOTPInterface extends OTPInterface * @return int The initial counter (a positive integer) */ public function getCounter(); + + /** + * @param int $counter + * + * @throws \InvalidArgumentException + * + * @return self + */ + public function setCounter($counter); } diff --git a/lib/OTP.php b/lib/OTP.php index 3b3081ee..6a4c94f1 100644 --- a/lib/OTP.php +++ b/lib/OTP.php @@ -6,6 +6,8 @@ abstract class OTP implements OTPInterface { + private $parameters = []; + /** * @param int $input * @@ -116,6 +118,151 @@ public function at($input) return $this->generateOTP($input); } + /** + * {@inheritdoc} + */ + public function getSecret() + { + return $this->getParameter('secret'); + } + + /** + * {@inheritdoc} + */ + public function setSecret($secret) + { + return $this->setParameter('secret', $secret); + } + + /** + * {@inheritdoc} + */ + public function getLabel() + { + return $this->getParameter('label'); + } + + /** + * {@inheritdoc} + */ + public function setLabel($label) + { + if ($this->hasSemicolon($label)) { + throw new \InvalidArgumentException('Label must not contain a semi-colon.'); + } + + return $this->setParameter('label', $label); + } + + /** + * {@inheritdoc} + */ + public function getIssuer() + { + return $this->getParameter('issuer'); + } + + /** + * {@inheritdoc} + */ + public function setIssuer($issuer) + { + if ($this->hasSemicolon($issuer)) { + throw new \InvalidArgumentException('Issuer must not contain a semi-colon.'); + } + + return $this->setParameter('issuer', $issuer); + } + + /** + * {@inheritdoc} + */ + public function isIssuerIncludedAsParameter() + { + return $this->getParameter('issuer_included_as_parameter'); + } + + /** + * {@inheritdoc} + */ + public function setIssuerIncludedAsParameter($issuer_included_as_parameter) + { + return $this->setParameter('issuer_included_as_parameter', $issuer_included_as_parameter); + } + + /** + * {@inheritdoc} + */ + public function getDigits() + { + return $this->getParameter('digits'); + } + + /** + * {@inheritdoc} + */ + public function setDigits($digits) + { + if (!is_numeric($digits) || $digits < 1) { + throw new \InvalidArgumentException('Digits must be at least 1.'); + } + + return $this->setParameter('digits', $digits); + } + + /** + * {@inheritdoc} + */ + public function getDigest() + { + return $this->getParameter('digest'); + } + + /** + * {@inheritdoc} + */ + public function setDigest($digest) + { + if (!in_array($digest, ['md5', 'sha1', 'sha256', 'sha512'])) { + throw new \InvalidArgumentException("'$digest' digest is not supported."); + } + + return $this->setParameter('digest', $digest); + } + + /** + * {@inheritdoc} + */ + public function getParameter($parameter) + { + if (array_key_exists($parameter, $this->parameters)) { + return $this->parameters[$parameter]; + } + return null; + } + + /** + * {@inheritdoc} + */ + public function setParameter($parameter, $value) + { + $this->parameters[$parameter] = $value; + + return $this; + } + + private function hasSemicolon($value) + { + $semicolons = [':', '%3A', '%3a']; + foreach ($semicolons as $semicolon) { + if (false !== strpos($value, $semicolon)) { + return true; + } + } + + return false; + } + /** * @throws \InvalidArgumentException * diff --git a/lib/OTPInterface.php b/lib/OTPInterface.php index aff9c6fa..680bb141 100644 --- a/lib/OTPInterface.php +++ b/lib/OTPInterface.php @@ -27,31 +27,96 @@ public function verify($otp, $input, $window = null); */ public function getSecret(); + /** + * @param string $secret + * + * @return self + */ + public function setSecret($secret); + /** * @return string The label of the OTP */ public function getLabel(); + /** + * @param string $label + * + * @throws \InvalidArgumentException + * + * @return self + */ + public function setLabel($label); + /** * @return string The issuer */ public function getIssuer(); + /** + * @param string $issuer + * + * @throws \InvalidArgumentException + * + * @return self + */ + public function setIssuer($issuer); + /** * @return bool If true, the issuer will be added as a parameter in the provisioning URI */ public function isIssuerIncludedAsParameter(); + /** + * @param bool $issuer_included_as_parameter + * + * @return self + */ + public function setIssuerIncludedAsParameter($issuer_included_as_parameter); + /** * @return int Number of digits in the OTP */ public function getDigits(); + /** + * @param int $digits + * + * @throws \InvalidArgumentException + * + * @return self + */ + public function setDigits($digits); + /** * @return string Digest algorithm used to calculate the OTP. Possible values are 'md5', 'sha1', 'sha256' and 'sha512' */ public function getDigest(); + /** + * @param string $digest + * + * @throws \InvalidArgumentException + * + * @return self + */ + public function setDigest($digest); + + /** + * @param string $parameter + * + * @return null|mixed + */ + public function getParameter($parameter); + + /** + * @param string $parameter + * @param mixed $value + * + * @return self + */ + public function setParameter($parameter, $value); + /** * @param bool $google_compatible If true (default), will produce provisioning URI compatible with Google Authenticator. Only applicable if algorithm="sha1", period=30 and digits=6. * diff --git a/lib/TOTP.php b/lib/TOTP.php index e2884c7a..28a6376f 100644 --- a/lib/TOTP.php +++ b/lib/TOTP.php @@ -2,8 +2,29 @@ namespace OTPHP; -abstract class TOTP extends OTP implements TOTPInterface +class TOTP extends OTP implements TOTPInterface { + + /** + * {@inheritdoc} + */ + public function setInterval($interval) + { + if (!is_int($interval) || $interval < 1) { + throw new \InvalidArgumentException('Interval must be at least 1.'); + } + + return $this->setParameter('interval', $interval); + } + + /** + * {@inheritdoc} + */ + public function getInterval() + { + return $this->getParameter('interval'); + } + /** * {@inheritdoc} */ diff --git a/lib/TOTPInterface.php b/lib/TOTPInterface.php index 7aa4618a..538a4cbf 100644 --- a/lib/TOTPInterface.php +++ b/lib/TOTPInterface.php @@ -13,4 +13,13 @@ public function now(); * @return int Get the interval of time for OTP generation (a non-null positive integer, in second) */ public function getInterval(); + + /** + * @param int $interval + * + * @throws \InvalidArgumentException + * + * @return self + */ + public function setInterval($interval); } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index c7008ce7..65465190 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -21,7 +21,6 @@ ./ ./tests - ./examples ./doc ./vendor diff --git a/tests/HOTPTest.php b/tests/HOTPTest.php index af260b4a..1304a33c 100644 --- a/tests/HOTPTest.php +++ b/tests/HOTPTest.php @@ -4,141 +4,46 @@ class HOTPTest extends \PHPUnit_Framework_TestCase { public function testGetProvisioningUri() { - $otp = $this->getMockBuilder('OTPHP\HOTP') - ->setMethods(['getSecret', 'getDigits', 'getDigest', 'getIssuer', 'getLabel', 'isIssuerIncludedAsParameter', 'getCounter', 'updateCounter']) - ->getMock(); - - $otp->expects($this->any()) - ->method('getLabel') - ->will($this->returnValue('alice@foo.bar')); - - $otp->expects($this->any()) - ->method('getSecret') - ->will($this->returnValue('JDDK4U6G3BJLEZ7Y')); - - $otp->expects($this->any()) - ->method('getIssuer') - ->will($this->returnValue('My Project')); - - $otp->expects($this->any()) - ->method('getDigest') - ->will($this->returnValue('sha1')); - - $otp->expects($this->any()) - ->method('getDigits') - ->will($this->returnValue(8)); - - $otp->expects($this->any()) - ->method('getCounter') - ->will($this->returnValue(1000)); - - $otp->expects($this->never()) - ->method('updateCounter'); + $otp = $this->createHOTP(8, 'sha1', 1000); $this->assertEquals('otpauth://hotp/My%20Project%3Aalice%40foo.bar?counter=1000&digits=8&secret=JDDK4U6G3BJLEZ7Y', $otp->getProvisioningUri()); } public function testVerifyCounterInvalid() { - $otp = $this->getMockBuilder('OTPHP\HOTP') - ->setMethods(['getSecret', 'getDigits', 'getDigest', 'getIssuer', 'getLabel', 'isIssuerIncludedAsParameter', 'getCounter', 'updateCounter']) - ->getMock(); - - $otp->expects($this->any()) - ->method('getLabel') - ->will($this->returnValue('alice@foo.bar')); - - $otp->expects($this->any()) - ->method('getSecret') - ->will($this->returnValue('JDDK4U6G3BJLEZ7Y')); - - $otp->expects($this->any()) - ->method('getIssuer') - ->will($this->returnValue('My Project')); - - $otp->expects($this->any()) - ->method('getDigest') - ->will($this->returnValue('sha1')); - - $otp->expects($this->any()) - ->method('getDigits') - ->will($this->returnValue(8)); - - $otp->expects($this->any()) - ->method('getCounter') - ->will($this->returnValue(1000)); - - $otp->expects($this->never()) - ->method('updateCounter'); + $otp = $this->createHOTP(8, 'sha1', 1000); $this->assertFalse($otp->verify(0, 100)); } public function testVerifyCounterChanged() { - $otp = $this->getMockBuilder('OTPHP\HOTP') - ->setMethods(['getSecret', 'getDigits', 'getDigest', 'getIssuer', 'getLabel', 'isIssuerIncludedAsParameter', 'getCounter', 'updateCounter']) - ->getMock(); - - $otp->expects($this->any()) - ->method('getLabel') - ->will($this->returnValue('alice@foo.bar')); - - $otp->expects($this->any()) - ->method('getSecret') - ->will($this->returnValue('JDDK4U6G3BJLEZ7Y')); - - $otp->expects($this->any()) - ->method('getIssuer') - ->will($this->returnValue('My Project')); - - $otp->expects($this->any()) - ->method('getDigest') - ->will($this->returnValue('sha1')); - - $otp->expects($this->any()) - ->method('getDigits') - ->will($this->returnValue(8)); - - $otp->method('getCounter') - ->will($this->onConsecutiveCalls(1000, 1100, 1100)); - - $otp->expects($this->once()) - ->method('updateCounter') - ->with(1101); + $otp = $this->createHOTP(8, 'sha1', 1000); $this->assertTrue($otp->verify('98449994', 1100)); $this->assertFalse($otp->verify('11111111', 1099)); + $this->assertTrue($otp->getCounter() === 1101); } public function testVerifyValidInWindow() { - $otp = $this->getMockBuilder('OTPHP\HOTP') - ->setMethods(['getSecret', 'getDigits', 'getDigest', 'getIssuer', 'getLabel', 'isIssuerIncludedAsParameter', 'getCounter', 'updateCounter']) - ->getMock(); - - $otp->expects($this->any()) - ->method('getLabel') - ->will($this->returnValue('alice@foo.bar')); - - $otp->expects($this->any()) - ->method('getSecret') - ->will($this->returnValue('JDDK4U6G3BJLEZ7Y')); - - $otp->expects($this->any()) - ->method('getIssuer') - ->will($this->returnValue('My Project')); - - $otp->expects($this->any()) - ->method('getDigest') - ->will($this->returnValue('sha1')); - - $otp->expects($this->any()) - ->method('getDigits') - ->will($this->returnValue(8)); + $otp = $this->createHOTP(8, 'sha1', 1000); $this->assertTrue($otp->verify('59647237', 1000, 50)); - $this->assertFalse($otp->verify('51642065', 1000, 50)); - $this->assertTrue($otp->verify('51642065', 1000, 100)); + $this->assertFalse($otp->verify('59647237', 1000, 50)); + $this->assertFalse($otp->verify('59647237', 2000, 50)); + } + + private function createHOTP($digits, $digest, $counter, $secret = 'JDDK4U6G3BJLEZ7Y', $label = 'alice@foo.bar', $issuer = 'My Project') + { + $otp = new \OTPHP\HOTP(); + $otp->setLabel($label) + ->setDigest($digest) + ->setDigits($digits) + ->setSecret($secret) + ->setIssuer($issuer) + ->setCounter($counter); + + return $otp; } } diff --git a/tests/OTPTest.php b/tests/OTPTest.php deleted file mode 100644 index b4c03a67..00000000 --- a/tests/OTPTest.php +++ /dev/null @@ -1,92 +0,0 @@ -getMockBuilder('OTPHP\OTP') - ->setMethods(['verify', 'getSecret', 'getDigits', 'getDigest', 'getIssuer', 'getLabel', 'isIssuerIncludedAsParameter', 'getProvisioningUri']) - ->getMock(); - - $otp->expects($this->any()) - ->method('getSecret') - ->will($this->returnValue('JDDK4U6G3BJLEZ7Y')); - - $otp->expects($this->any()) - ->method('getDigits') - ->will($this->returnValue(6)); - - $otp->expects($this->any()) - ->method('getDigest') - ->will($this->returnValue('sha1')); - - $this->assertEquals('855783', $otp->at(0)); - $this->assertEquals('549607', $otp->at(500)); - $this->assertEquals('654666', $otp->at(1500)); - } - - /** - * @expectedException Exception - */ - public function testGenerateUriWithoutLabel() - { - $otp = $this->getMockBuilder('OTPHP\OTP') - ->getMock(); - - $method = self::getMethod('generateURI'); - - $method->invokeArgs($otp, ['test', [], true]); - } - - public function testGenerateUriWithValidLabel() - { - $otp = $this->getMockBuilder('OTPHP\OTP') - ->setMethods(['verify', 'getSecret', 'getDigits', 'getDigest', 'getIssuer', 'getLabel', 'isIssuerIncludedAsParameter', 'getProvisioningUri']) - ->getMock(); - - $otp->expects($this->any()) - ->method('getLabel') - ->will($this->returnValue('alice@foo.bar')); - - $otp->expects($this->any()) - ->method('getSecret') - ->will($this->returnValue('JDDK4U6G3BJLEZ7Y')); - - $method = self::getMethod('generateURI'); - - $this->assertEquals('otpauth://test/alice%40foo.bar?secret=JDDK4U6G3BJLEZ7Y', $method->invokeArgs($otp, ['test', [], true])); - $this->assertEquals('otpauth://test/alice%40foo.bar?option1=baz&secret=JDDK4U6G3BJLEZ7Y', $method->invokeArgs($otp, ['test', ['option1' => 'baz'], true])); - - $otp->expects($this->any()) - ->method('getIssuer') - ->will($this->returnValue('My Project')); - - $otp->expects($this->any()) - ->method('getDigest') - ->will($this->returnValue('sha1')); - - $otp->expects($this->any()) - ->method('getDigits') - ->will($this->returnValue(8)); - - $this->assertEquals('otpauth://test/My%20Project%3Aalice%40foo.bar?digits=8&secret=JDDK4U6G3BJLEZ7Y', $method->invokeArgs($otp, ['test', [], true])); - - $otp->expects($this->any()) - ->method('isIssuerIncludedAsParameter') - ->will($this->returnValue(true)); - - $this->assertEquals('otpauth://test/My%20Project%3Aalice%40foo.bar?digits=8&issuer=My%20Project&secret=JDDK4U6G3BJLEZ7Y', $method->invokeArgs($otp, ['test', [], true])); - } - - /** - * @param string $name - */ - protected static function getMethod($name) - { - $class = new \ReflectionClass('OTPHP\OTP'); - $method = $class->getMethod($name); - $method->setAccessible(true); - - return $method; - } -} diff --git a/tests/TOTPTest.php b/tests/TOTPTest.php index eb59b867..f0f019cc 100644 --- a/tests/TOTPTest.php +++ b/tests/TOTPTest.php @@ -6,14 +6,14 @@ class TOTPTest extends \PHPUnit_Framework_TestCase { public function testGetProvisioningUri() { - $otp = $this->creatTOTP(6, 'sha1', 30); + $otp = $this->createTOTP(6, 'sha1', 30); $this->assertEquals('otpauth://totp/My%20Project%3Aalice%40foo.bar?secret=JDDK4U6G3BJLEZ7Y', $otp->getProvisioningUri()); } public function testGenerateOtpAt() { - $otp = $this->creatTOTP(6, 'sha1', 30); + $otp = $this->createTOTP(6, 'sha1', 30); $this->assertEquals('855783', $otp->at(0)); $this->assertEquals('762124', $otp->at(319690800)); @@ -22,14 +22,14 @@ public function testGenerateOtpAt() public function testGenerateOtpNow() { - $otp = $this->creatTOTP(6, 'sha1', 30); + $otp = $this->createTOTP(6, 'sha1', 30); $this->assertEquals($otp->now(), $otp->at(time())); } public function testVerifyOtpNow() { - $otp = $this->creatTOTP(6, 'sha1', 30); + $otp = $this->createTOTP(6, 'sha1', 30); $totp = $otp->at(time()); $this->assertTrue($otp->verify($totp)); @@ -37,7 +37,7 @@ public function testVerifyOtpNow() public function testVerifyOtp() { - $otp = $this->creatTOTP(6, 'sha1', 30); + $otp = $this->createTOTP(6, 'sha1', 30); $this->assertTrue($otp->verify('855783', 0)); $this->assertTrue($otp->verify('762124', 319690800)); @@ -50,7 +50,7 @@ public function testVerifyOtp() public function testNotCompatibleWithGoogleAuthenticator() { - $otp = $this->creatTOTP(9, 'sha512', 10); + $otp = $this->createTOTP(9, 'sha512', 10); $this->assertEquals('otpauth://totp/My%20Project%3Aalice%40foo.bar?algorithm=sha512&digits=9&period=10&secret=JDDK4U6G3BJLEZ7Y', $otp->getProvisioningUri()); } @@ -70,9 +70,9 @@ public function testVectors($totp, $timestamp, $expected_value) */ public function testVectorsData() { - $totp_sha1 = $this->creatTOTP(8, 'sha1', 30, Base32::encode('12345678901234567890')); - $totp_sha256 = $this->creatTOTP(8, 'sha256', 30, Base32::encode('12345678901234567890123456789012')); - $totp_sha512 = $this->creatTOTP(8, 'sha512', 30, Base32::encode('1234567890123456789012345678901234567890123456789012345678901234')); + $totp_sha1 = $this->createTOTP(8, 'sha1', 30, Base32::encode('12345678901234567890')); + $totp_sha256 = $this->createTOTP(8, 'sha256', 30, Base32::encode('12345678901234567890123456789012')); + $totp_sha512 = $this->createTOTP(8, 'sha512', 30, Base32::encode('1234567890123456789012345678901234567890123456789012345678901234')); return [ [$totp_sha1, 59, '94287082'], @@ -98,14 +98,14 @@ public function testVectorsData() public function testWithoutGoogleAuthenticatorCompatibility() { - $otp = $this->creatTOTP(6, 'sha1', 30); + $otp = $this->createTOTP(6, 'sha1', 30); $this->assertEquals('otpauth://totp/My%20Project%3Aalice%40foo.bar?algorithm=sha1&digits=6&period=30&secret=JDDK4U6G3BJLEZ7Y', $otp->getProvisioningUri(false)); } public function testVerifyOtpInWindow() { - $otp = $this->creatTOTP(6, 'sha1', 30); + $otp = $this->createTOTP(6, 'sha1', 30); $this->assertFalse($otp->verify('054409', 319690800, 10)); // -11 intervals $this->assertTrue($otp->verify('808167', 319690800, 10)); // -10 intervals @@ -116,35 +116,16 @@ public function testVerifyOtpInWindow() $this->assertFalse($otp->verify('465009', 319690800, 10)); // +11 intervals } - private function creatTOTP($digits, $digest, $interval, $secret = 'JDDK4U6G3BJLEZ7Y', $label = 'alice@foo.bar', $issuer = 'My Project') + private function createTOTP($digits, $digest, $interval, $secret = 'JDDK4U6G3BJLEZ7Y', $label = 'alice@foo.bar', $issuer = 'My Project') { - $otp = $this->getMockBuilder('OTPHP\TOTP') - ->setMethods(['getSecret', 'getDigits', 'getDigest', 'getIssuer', 'getLabel', 'isIssuerIncludedAsParameter', 'getInterval']) - ->getMock(); - - $otp->expects($this->any()) - ->method('getLabel') - ->will($this->returnValue($label)); - - $otp->expects($this->any()) - ->method('getSecret') - ->will($this->returnValue($secret)); - - $otp->expects($this->any()) - ->method('getIssuer') - ->will($this->returnValue($issuer)); - - $otp->expects($this->any()) - ->method('getDigest') - ->will($this->returnValue($digest)); - - $otp->expects($this->any()) - ->method('getDigits') - ->will($this->returnValue($digits)); - - $otp->expects($this->any()) - ->method('getInterval') - ->will($this->returnValue($interval)); + $otp = new \OTPHP\TOTP(); + $otp->setLabel($label) + ->setDigest($digest) + ->setDigits($digits) + ->setSecret($secret) + ->setIssuer($issuer) + ->setIssuerIncludedAsParameter(false) + ->setInterval($interval); return $otp; }