Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental tidying-up branch #4

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
84 changes: 60 additions & 24 deletions src/GAuth/Auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,40 @@ class Auth
* Internal lookup table
* @var array
*/
private $lookup = array();
private $lookup = array(
'A' => 0,
'B' => 1,
'C' => 2,
'D' => 3,
'E' => 4,
'F' => 5,
'G' => 6,
'H' => 7,
'I' => 8,
'J' => 9,
'K' => 10,
'L' => 11,
'M' => 12,
'N' => 13,
'O' => 14,
'P' => 15,
'Q' => 16,
'R' => 17,
'S' => 18,
'T' => 19,
'U' => 20,
'V' => 21,
'W' => 22,
'X' => 23,
'Y' => 24,
'Z' => 25,
2 => 26,
3 => 27,
4 => 28,
5 => 29,
6 => 30,
7 => 31,
);

/**
* Initialization key
Expand Down Expand Up @@ -54,8 +87,6 @@ class Auth
*/
public function __construct($initKey = null)
{
$this->buildLookup();

if ($initKey !== null) {
$this->setInitKey($initKey);
}
Expand All @@ -64,15 +95,12 @@ public function __construct($initKey = null)
/**
* Build the base32 lookup table
*
* @deprecated Hard-coded in.
* @return null
*/
public function buildLookup()
{
$lookup = array_combine(
array_merge(range('A', 'Z'), range(2, 7)),
range(0, 31)
);
$this->setLookup($lookup);
trigger_error('The base32 lookup table now comes pre-built.', E_USER_DEPRECATED);
}

/**
Expand Down Expand Up @@ -108,7 +136,7 @@ public function setRange($range)
*/
public function setInitKey($key)
{
if (preg_match('/^['.implode('', array_keys($this->getLookup())).']+$/', $key) == false) {
if (preg_match('/^['.implode('', array_keys($this->lookup)).']+$/', $key) == false) {
throw new \InvalidArgumentException('Invalid base32 hash!');
}
$this->initKey = $key;
Expand All @@ -134,11 +162,7 @@ public function getInitKey()
*/
public function setLookup($lookup)
{
if (!is_array($lookup)) {
throw new \InvalidArgumentException('Lookup value must be an array');
}
$this->lookup = $lookup;
return $this;
trigger_error('The base 32 lookup should not ever be overwritten.', E_USER_DEPRECATED);
}

/**
Expand All @@ -148,6 +172,7 @@ public function setLookup($lookup)
*/
public function getLookup()
{
trigger_error('This method will be removed in a later version', E_USER_DEPRECATED);
return $this->lookup;
}

Expand All @@ -171,7 +196,7 @@ public function getRefresh()
public function setRefresh($seconds)
{
if (!is_numeric($seconds)) {
throw \InvalidArgumentException('Seconds must be numeric');
throw new \InvalidArgumentException('Seconds must be numeric');
}
$this->refreshSeconds = $seconds;
return $this;
Expand Down Expand Up @@ -215,9 +240,20 @@ public function validateCode($code, $initKey = null, $timestamp = null, $range =
throw new \InvalidArgumentException('Incorrect code length');
}

$range = ($range == null) ? $this->getRange() : $range;
$timestamp = ($timestamp == null) ? $this->generateTimestamp() : $timestamp;
$initKey = ($initKey == null) ? $this->getInitKey() : $initKey;
if ($initKey) {
$this->setInitKey($initKey);
}
$initKey = $this->getInitKey();

if ($timestamp === null) {
$timestamp = $this->generateTimestamp();
}

if ($range) {
$this->setRange($range);
}
$range = $this->getRange();


$binary = $this->base32_decode($initKey);

Expand All @@ -236,7 +272,7 @@ public function validateCode($code, $initKey = null, $timestamp = null, $range =
* @param string $timestamp Timestamp for calculation [optional]
* @return string Geneerated code/hash
*/
public function generateOneTime($initKey = null, $timestamp = null)
private function generateOneTime($initKey = null, $timestamp = null)
{
$initKey = ($initKey == null) ? $this->getInitKey() : $initKey;
$timestamp = ($timestamp == null) ? $this->generateTimestamp() : $timestamp;
Expand All @@ -260,7 +296,7 @@ public function generateOneTime($initKey = null, $timestamp = null)
*/
public function generateCode($length = 16)
{
$lookup = implode('', array_keys($this->getLookup()));
$lookup = implode('', array_keys($this->lookup));
$code = '';

for ($i = 0; $i < $length; $i++) {
Expand All @@ -275,7 +311,7 @@ public function generateCode($length = 16)
*
* @return integer Timestamp
*/
public function generateTimestamp()
private function generateTimestamp()
{
return floor(microtime(true)/$this->getRefresh());
}
Expand All @@ -286,7 +322,7 @@ public function generateTimestamp()
* @param string $hash Hash to truncate
* @return string Truncated hash value
*/
public function truncateHash($hash)
private function truncateHash($hash)
{
$offset = ord($hash[19]) & 0xf;

Expand All @@ -305,9 +341,9 @@ public function truncateHash($hash)
* @throws \InvalidArgumentException When hash is not valid
* @return string Binary value of hash
*/
public function base32_decode($hash)
private function base32_decode($hash)
{
$lookup = $this->getLookup();
$lookup = $this->lookup;

if (preg_match('/^['.implode('', array_keys($lookup)).']+$/', $hash) == false) {
throw new \InvalidArgumentException('Invalid base32 hash!');
Expand Down
3 changes: 3 additions & 0 deletions tests/bootstrap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

require_once dirname(__DIR__) . '/src/GAuth/Auth.php';
7 changes: 7 additions & 0 deletions tests/phpunit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<phpunit bootstrap="./bootstrap.php">
<testsuites>
<testsuite>
<directory>./src</directory>
</testsuite>
</testsuites>
</phpunit>
192 changes: 192 additions & 0 deletions tests/src/AuthTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
<?php

namespace GAuth;

class AuthTest extends \PHPUnit_Framework_TestCase {


public function test_instantiate () {
$auth = new Auth();
}


public function test_invalid_init_key_fails () {

$this->setExpectedException("InvalidArgumentException", "Invalid base32 hash!");
new Auth('~');
}


public function test_valid_init_key_gets_set () {
$key = 'ABC';
$auth = new Auth($key);
$this->assertEquals($key, $auth->getInitKey(), 'Valid key can be got');
}




public function test_get_and_set_range () {

$range = 54;
$auth = new Auth();
$auth->setRange($range);
$this->assertEquals($range, $auth->getRange(), 'Range is set correctly');
}

public function test_invalid_range_fails () {

$this->setExpectedException('InvalidArgumentException');
$auth = new Auth();
$auth->setRange("cat");

}


public function test_set_and_get_refresh () {

$refresh = 43;
$auth = new Auth();
$auth->setRefresh($refresh);
$this->assertEquals($refresh, $auth->getRefresh(), 'Refresh is set and got OK');

}


public function test_invalid_refresh_fails () {

$this->setExpectedException('InvalidArgumentException');
$auth = new Auth();
$auth->setRefresh("litter");

}


public function test_set_and_get_code_length () {

$length = 123;
$auth = new Auth();
$auth->setCodeLength($length);
$this->assertEquals($length, $auth->getCodeLength());
}


public function test_generate_a_init_code () {

$auth = new Auth();
$code = $auth->generateCode();
$this->assertEquals(16, strlen($code), 'Default code is 16 chars');

$base64values = array_keys($this->base64values);
$code_letters = str_split($code);

foreach ($code_letters as $letter) {
$this->assertContains($letter, $base64values, 'Key consists of base 64 values');
}
}


public function test_generate_a_code_of_length () {
$auth = new Auth();
$code = $auth->generateCode(4);
$this->assertEquals(4, strlen($code), 'Default code is 16 chars');
}


public function test_validate_a_valid_code () {

$key = 'ABC';
$auth = new Auth($key);
$code = '424535';
$timestamp = 1421707340;

$result = $auth->validateCode($code, null, $timestamp);
$this->assertTrue($result, 'This is a known good code');

}


public function test_validate_a_valid_code_false_when_out_of_range () {

$key = 'ABC';
$code = '424535';
$timestamp = 1421707340;

$auth = new Auth($key);
$auth->setRange(1);

$result = $auth->validateCode($code, null, $timestamp);
$this->assertFalse($result, 'This was a known good code 2 seconds ago');
}


public function test_validate_a_code_of_wrong_length_fails () {

$this->setExpectedException('InvalidArgumentException');

$auth = new Auth();
$auth->setCodeLength(10);
$auth->validateCode('lt10');

}


public function test_validate_with_different_init_key_is_false () {

$key = 'DEF';
$auth = new Auth($key);
$code = '424535';
$timestamp = 1421707340;

$result = $auth->validateCode($code, null, $timestamp);
$this->assertFalse($result, 'This is a known good code for key ABC');

}


public function test_validate_after_time_has_passed_is_false () {

$key = 'ABC';
$auth = new Auth($key);
$code = '424535';

$result = $auth->validateCode($code);
$this->assertFalse($result, 'This was a known good code, but that was then');
}


private $base64values = array(
'A' => 0,
'B' => 1,
'C' => 2,
'D' => 3,
'E' => 4,
'F' => 5,
'G' => 6,
'H' => 7,
'I' => 8,
'J' => 9,
'K' => 10,
'L' => 11,
'M' => 12,
'N' => 13,
'O' => 14,
'P' => 15,
'Q' => 16,
'R' => 17,
'S' => 18,
'T' => 19,
'U' => 20,
'V' => 21,
'W' => 22,
'X' => 23,
'Y' => 24,
'Z' => 25,
2 => 26,
3 => 27,
4 => 28,
5 => 29,
6 => 30,
7 => 31,
);
}