diff --git a/.travis.yml b/.travis.yml index d6fa336..f1d5777 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,4 +12,4 @@ install: - export PATH="$HOME/.composer/vendor/bin:$PATH" - composer install -script: phpunit --coverage-text --configuration tests/phpunit.xml +script: vendor/bin/phpunit --coverage-text --configuration tests/phpunit.xml diff --git a/PHPGangsta/GoogleAuthenticator.php b/PHPGangsta/GoogleAuthenticator.php index 02c645f..4f88313 100644 --- a/PHPGangsta/GoogleAuthenticator.php +++ b/PHPGangsta/GoogleAuthenticator.php @@ -1,16 +1,19 @@ _getBase32LookupTable(); // Valid secret lengths are 80 to 640 bits @@ -30,7 +33,7 @@ public function createSecret($secretLength = 16) throw new Exception('Bad secret length'); } $secret = ''; - $rnd = false; + $rnd = false; if (function_exists('random_bytes')) { $rnd = random_bytes($secretLength); } elseif (function_exists('mcrypt_create_iv')) { @@ -60,8 +63,7 @@ public function createSecret($secretLength = 16) * * @return string */ - public function getCode($secret, $timeSlice = null) - { + public function getCode($secret, $timeSlice = null) { if ($timeSlice === null) { $timeSlice = floor(time() / 30); } @@ -69,7 +71,7 @@ public function getCode($secret, $timeSlice = null) $secretkey = $this->_base32Decode($secret); // Pack time into binary string - $time = chr(0).chr(0).chr(0).chr(0).pack('N*', $timeSlice); + $time = chr(0) . chr(0) . chr(0) . chr(0) . pack('N*', $timeSlice); // Hash it with users secret key $hm = hash_hmac('SHA1', $time, $secretkey, true); // Use last nipple of result as index/offset @@ -98,18 +100,17 @@ public function getCode($secret, $timeSlice = null) * * @return string */ - public function getQRCodeGoogleUrl($name, $secret, $title = null, $params = array()) - { - $width = !empty($params['width']) && (int) $params['width'] > 0 ? (int) $params['width'] : 200; - $height = !empty($params['height']) && (int) $params['height'] > 0 ? (int) $params['height'] : 200; - $level = !empty($params['level']) && array_search($params['level'], array('L', 'M', 'Q', 'H')) !== false ? $params['level'] : 'M'; + public function getQRCodeGoogleUrl($name, $secret, $title = null, $params = array()) { + $width = !empty($params['width']) && (int)$params['width'] > 0 ? (int)$params['width'] : 200; + $height = !empty($params['height']) && (int)$params['height'] > 0 ? (int)$params['height'] : 200; + $level = !empty($params['level']) && array_search($params['level'], array('L', 'M', 'Q', 'H')) !== false ? $params['level'] : 'M'; - $urlencoded = urlencode('otpauth://totp/'.$name.'?secret='.$secret.''); - if (isset($title)) { - $urlencoded .= urlencode('&issuer='.urlencode($title)); - } + $otpauthUrl = $this->getOtpauthUrl($name, $secret, $title); - return 'https://chart.googleapis.com/chart?chs='.$width.'x'.$height.'&chld='.$level.'|0&cht=qr&chl='.$urlencoded.''; + return 'https://chart.googleapis.com/chart?chs=' . + $width . 'x' . $height . + '&chld=' . $level . + '|0&cht=qr&chl=' . urlencode($otpauthUrl); } /** @@ -122,8 +123,7 @@ public function getQRCodeGoogleUrl($name, $secret, $title = null, $params = arra * * @return bool */ - public function verifyCode($secret, $code, $discrepancy = 1, $currentTimeSlice = null) - { + public function verifyCode($secret, $code, $discrepancy = 1, $currentTimeSlice = null) { if ($currentTimeSlice === null) { $currentTimeSlice = floor(time() / 30); } @@ -147,10 +147,9 @@ public function verifyCode($secret, $code, $discrepancy = 1, $currentTimeSlice = * * @param int $length * - * @return PHPGangsta_GoogleAuthenticator + * @return GoogleAuthenticator */ - public function setCodeLength($length) - { + public function setCodeLength($length) { $this->_codeLength = $length; return $this; @@ -163,28 +162,28 @@ public function setCodeLength($length) * * @return bool|string */ - protected function _base32Decode($secret) - { + protected function _base32Decode($secret) { if (empty($secret)) { return ''; } - $base32chars = $this->_getBase32LookupTable(); + $base32chars = $this->_getBase32LookupTable(); $base32charsFlipped = array_flip($base32chars); $paddingCharCount = substr_count($secret, $base32chars[32]); - $allowedValues = array(6, 4, 3, 1, 0); + $allowedValues = array(6, 4, 3, 1, 0); if (!in_array($paddingCharCount, $allowedValues)) { return false; } for ($i = 0; $i < 4; ++$i) { - if ($paddingCharCount == $allowedValues[$i] && - substr($secret, -($allowedValues[$i])) != str_repeat($base32chars[32], $allowedValues[$i])) { + if ($paddingCharCount == $allowedValues[$i] + && substr($secret, -($allowedValues[$i])) != str_repeat($base32chars[32], $allowedValues[$i]) + ) { return false; } } - $secret = str_replace('=', '', $secret); - $secret = str_split($secret); + $secret = str_replace('=', '', $secret); + $secret = str_split($secret); $binaryString = ''; for ($i = 0; $i < count($secret); $i = $i + 8) { $x = ''; @@ -208,8 +207,7 @@ protected function _base32Decode($secret) * * @return array */ - protected function _getBase32LookupTable() - { + protected function _getBase32LookupTable() { return array( 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 7 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 15 @@ -228,8 +226,7 @@ protected function _getBase32LookupTable() * * @return bool True if the two strings are identical */ - private function timingSafeEquals($safeString, $userString) - { + private function timingSafeEquals($safeString, $userString) { if (function_exists('hash_equals')) { return hash_equals($safeString, $userString); } @@ -249,4 +246,22 @@ private function timingSafeEquals($safeString, $userString) // They are only identical strings if $result is exactly 0... return $result === 0; } + + /** + * getOtpauthUrl + * + * @param string $name + * @param string $secret + * @param string $title + * + * @return string + */ + public function getOtpauthUrl($name, $secret, $title) { + $otpauthUrl = 'otpauth://totp/' . urlencode($name) . '?secret=' . urlencode($secret); + if (isset($title)) { + $otpauthUrl .= '&issuer=' . urlencode($title); + } + + return $otpauthUrl; + } } diff --git a/composer.json b/composer.json index 9de7cc1..6428bf8 100644 --- a/composer.json +++ b/composer.json @@ -20,9 +20,12 @@ "require": { "php": ">=5.3" }, - "autoload": { - "classmap": ["PHPGangsta/GoogleAuthenticator.php"] - } + "require-dev": { + "phpunit/phpunit": "*" + }, + "autoload": { + "classmap": ["PHPGangsta/GoogleAuthenticator.php"] + } } diff --git a/tests/GoogleAuthenticatorTest.php b/tests/GoogleAuthenticatorTest.php index 56fc2c1..da1d56a 100644 --- a/tests/GoogleAuthenticatorTest.php +++ b/tests/GoogleAuthenticatorTest.php @@ -1,19 +1,32 @@ googleAuthenticator = new PHPGangsta_GoogleAuthenticator(); + /** + * setUp + * + * @return void + */ + protected function setUp() { + $this->googleAuthenticator = new GoogleAuthenticator(); } - public function codeProvider() - { + /** + * codeProvider + * + * @return array + */ + public function codeProvider() { // Secret, time, code return array( array('SECRET', '0', '200470'), @@ -22,23 +35,35 @@ public function codeProvider() ); } - public function testItCanBeInstantiated() - { - $ga = new PHPGangsta_GoogleAuthenticator(); + /** + * testItCanBeInstantiated + * + * @return void + */ + public function testItCanBeInstantiated() { + $ga = $this->googleAuthenticator; - $this->assertInstanceOf('PHPGangsta_GoogleAuthenticator', $ga); + $this->assertInstanceOf("PHPGangsta\GoogleAuthenticator", $ga); } - public function testCreateSecretDefaultsToSixteenCharacters() - { - $ga = $this->googleAuthenticator; + /** + * testCreateSecretDefaultsToSixteenCharacters + * + * @return void + */ + public function testCreateSecretDefaultsToSixteenCharacters() { + $ga = $this->googleAuthenticator; $secret = $ga->createSecret(); $this->assertEquals(strlen($secret), 16); } - public function testCreateSecretLengthCanBeSpecified() - { + /** + * testCreateSecretLengthCanBeSpecified + * + * @return void + */ + public function testCreateSecretLengthCanBeSpecified() { $ga = $this->googleAuthenticator; for ($secretLength = 16; $secretLength < 100; ++$secretLength) { @@ -49,20 +74,31 @@ public function testCreateSecretLengthCanBeSpecified() } /** + * testGetCodeReturnsCorrectValues + * * @dataProvider codeProvider + * + * @param $secret + * @param $timeSlice + * @param $code + * + * @return void */ - public function testGetCodeReturnsCorrectValues($secret, $timeSlice, $code) - { + public function testGetCodeReturnsCorrectValues($secret, $timeSlice, $code) { $generatedCode = $this->googleAuthenticator->getCode($secret, $timeSlice); $this->assertEquals($code, $generatedCode); } - public function testGetQRCodeGoogleUrlReturnsCorrectUrl() - { - $secret = 'SECRET'; - $name = 'Test'; - $url = $this->googleAuthenticator->getQRCodeGoogleUrl($name, $secret); + /** + * testGetQRCodeGoogleUrlReturnsCorrectUrl + * + * @return void + */ + public function testGetQRCodeGoogleUrlReturnsCorrectUrl() { + $secret = 'SECRET'; + $name = 'Test'; + $url = $this->googleAuthenticator->getQRCodeGoogleUrl($name, $secret); $urlParts = parse_url($url); parse_str($urlParts['query'], $queryStringArray); @@ -71,41 +107,54 @@ public function testGetQRCodeGoogleUrlReturnsCorrectUrl() $this->assertEquals($urlParts['host'], 'chart.googleapis.com'); $this->assertEquals($urlParts['path'], '/chart'); - $expectedChl = 'otpauth://totp/'.$name.'?secret='.$secret; + $expectedChl = 'otpauth://totp/' . $name . '?secret=' . $secret; $this->assertEquals($queryStringArray['chl'], $expectedChl); } - public function testVerifyCode() - { + /** + * testVerifyCode + * + * @return void + */ + public function testVerifyCode() { $secret = 'SECRET'; - $code = $this->googleAuthenticator->getCode($secret); + $code = $this->googleAuthenticator->getCode($secret); $result = $this->googleAuthenticator->verifyCode($secret, $code); $this->assertEquals(true, $result); - $code = 'INVALIDCODE'; + $code = 'INVALIDCODE'; $result = $this->googleAuthenticator->verifyCode($secret, $code); $this->assertEquals(false, $result); } - public function testVerifyCodeWithLeadingZero() - { + /** + * testVerifyCodeWithLeadingZero + * + * @return void + */ + public function testVerifyCodeWithLeadingZero() { $secret = 'SECRET'; - $code = $this->googleAuthenticator->getCode($secret); + $code = $this->googleAuthenticator->getCode($secret); $result = $this->googleAuthenticator->verifyCode($secret, $code); $this->assertEquals(true, $result); - $code = '0'.$code; + $code = '0' . $code; $result = $this->googleAuthenticator->verifyCode($secret, $code); $this->assertEquals(false, $result); } - public function testSetCodeLength() - { + /** + * testSetCodeLength + * + * @return void + */ + public function testSetCodeLength() { $result = $this->googleAuthenticator->setCodeLength(6); - $this->assertInstanceOf('PHPGangsta_GoogleAuthenticator', $result); + $this->assertInstanceOf("PHPGangsta\GoogleAuthenticator", $result); } + }