make QR Code Provider a mandatory constructor argument

This change is discussed in #104
Currently, the library defaults to a QR Code Provider using an external
service, thus leaking secrets.

This change forces the definition of a QR Code Provider in the
constructor. It is a breaking change.

fixes #104
This commit is contained in:
Nicolas CARPi
2024-04-25 21:23:39 +02:00
parent cabcf5d493
commit 2080319f6f
10 changed files with 99 additions and 67 deletions
+11 -9
View File
@@ -7,6 +7,7 @@ namespace Tests\Providers\Qr;
use PHPUnit\Framework\TestCase;
use RobThree\Auth\Algorithm;
use RobThree\Auth\Providers\Qr\HandlesDataUri;
use RobThree\Auth\Providers\Qr\IQRCodeProvider;
use RobThree\Auth\TwoFactorAuth;
use RobThree\Auth\TwoFactorAuthException;
@@ -14,11 +15,16 @@ class IQRCodeProviderTest extends TestCase
{
use HandlesDataUri;
protected IQRCodeProvider $qr;
protected function setUp(): void
{
$this->qr = new TestQrProvider();
}
public function testTotpUriIsCorrect(): void
{
$qr = new TestQrProvider();
$tfa = new TwoFactorAuth('Test&Issuer', 6, 30, Algorithm::Sha1, $qr);
$tfa = new TwoFactorAuth($this->qr, 'Test&Issuer', 6, 30, Algorithm::Sha1);
$data = $this->DecodeDataUri($tfa->getQRCodeImageAsDataUri('Test&Label', 'VMR466AB62ZBOKHE'));
$this->assertSame('test/test', $data['mimetype']);
$this->assertSame('base64', $data['encoding']);
@@ -27,14 +33,12 @@ class IQRCodeProviderTest extends TestCase
public function testTotpUriIsCorrectNoIssuer(): void
{
$qr = new TestQrProvider();
/**
* The library specifies the issuer is null by default however in PHP 8.1
* there is a deprecation warning for passing null as a string argument to rawurlencode
*/
$tfa = new TwoFactorAuth(null, 6, 30, Algorithm::Sha1, $qr);
$tfa = new TwoFactorAuth($this->qr, null, 6, 30, Algorithm::Sha1);
$data = $this->DecodeDataUri($tfa->getQRCodeImageAsDataUri('Test&Label', 'VMR466AB62ZBOKHE'));
$this->assertSame('test/test', $data['mimetype']);
$this->assertSame('base64', $data['encoding']);
@@ -43,9 +47,7 @@ class IQRCodeProviderTest extends TestCase
public function testGetQRCodeImageAsDataUriThrowsOnInvalidSize(): void
{
$qr = new TestQrProvider();
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, $qr);
$tfa = new TwoFactorAuth($this->qr, 'Test', 6, 30, Algorithm::Sha1);
$this->expectException(TwoFactorAuthException::class);
+2 -1
View File
@@ -7,12 +7,13 @@ namespace Tests\Providers\Rng;
use PHPUnit\Framework\TestCase;
use RobThree\Auth\Algorithm;
use RobThree\Auth\TwoFactorAuth;
use Tests\Providers\Qr\TestQrProvider;
class IRNGProviderTest extends TestCase
{
public function testCreateSecret(): void
{
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, null);
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 6, 30, Algorithm::Sha1, null, null);
$this->assertIsString($tfa->createSecret());
}
}
+4 -3
View File
@@ -8,6 +8,7 @@ use PHPUnit\Framework\TestCase;
use RobThree\Auth\Algorithm;
use RobThree\Auth\TwoFactorAuth;
use RobThree\Auth\TwoFactorAuthException;
use Tests\Providers\Qr\TestQrProvider;
class ITimeProviderTest extends TestCase
{
@@ -17,7 +18,7 @@ class ITimeProviderTest extends TestCase
$tpr1 = new TestTimeProvider(123);
$tpr2 = new TestTimeProvider(128);
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, null, $tpr1);
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 6, 30, Algorithm::Sha1, null, $tpr1);
$tfa->ensureCorrectTime(array($tpr2)); // 128 - 123 = 5 => within default leniency
}
@@ -26,7 +27,7 @@ class ITimeProviderTest extends TestCase
$tpr1 = new TestTimeProvider(123);
$tpr2 = new TestTimeProvider(124);
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, null, $tpr1);
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 6, 30, Algorithm::Sha1, null, $tpr1);
$this->expectException(TwoFactorAuthException::class);
@@ -36,7 +37,7 @@ class ITimeProviderTest extends TestCase
public function testEnsureDefaultTimeProviderReturnsCorrectTime(): void
{
$this->expectNotToPerformAssertions();
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1);
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 6, 30, Algorithm::Sha1);
$tfa->ensureCorrectTime(array(new TestTimeProvider(time())), 1); // Use a leniency of 1, should the time change between both time() calls
}
}
+14 -13
View File
@@ -11,6 +11,7 @@ use RobThree\Auth\Providers\Time\HttpTimeProvider;
use RobThree\Auth\Providers\Time\NTPTimeProvider;
use RobThree\Auth\TwoFactorAuth;
use RobThree\Auth\TwoFactorAuthException;
use Tests\Providers\Qr\TestQrProvider;
class TwoFactorAuthTest extends TestCase
{
@@ -18,26 +19,26 @@ class TwoFactorAuthTest extends TestCase
{
$this->expectException(TwoFactorAuthException::class);
new TwoFactorAuth('Test', 0);
new TwoFactorAuth(new TestQrProvider(), 'Test', 0);
}
public function testConstructorThrowsOnInvalidPeriod(): void
{
$this->expectException(TwoFactorAuthException::class);
new TwoFactorAuth('Test', 6, 0);
new TwoFactorAuth(new TestQrProvider(), 'Test', 6, 0);
}
public function testGetCodeReturnsCorrectResults(): void
{
$tfa = new TwoFactorAuth('Test');
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test');
$this->assertSame('543160', $tfa->getCode('VMR466AB62ZBOKHE', 1426847216));
$this->assertSame('538532', $tfa->getCode('VMR466AB62ZBOKHE', 0));
}
public function testEnsureAllTimeProvidersReturnCorrectTime(): void
{
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1);
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 6, 30, Algorithm::Sha1);
$tfa->ensureCorrectTime(array(
new NTPTimeProvider(), // Uses pool.ntp.org by default
//new \RobThree\Auth\Providers\Time\NTPTimeProvider('time.google.com'), // Somehow time.google.com and time.windows.com make travis timeout??
@@ -50,7 +51,7 @@ class TwoFactorAuthTest extends TestCase
public function testVerifyCodeWorksCorrectly(): void
{
$tfa = new TwoFactorAuth('Test', 6, 30);
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 6, 30);
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847190));
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 0, 1426847190 + 29)); //Test discrepancy
$this->assertFalse($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 0, 1426847190 + 30)); //Test discrepancy
@@ -69,7 +70,7 @@ class TwoFactorAuthTest extends TestCase
public function testVerifyCorrectTimeSliceIsReturned(): void
{
$tfa = new TwoFactorAuth('Test', 6, 30);
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 6, 30);
// We test with discrepancy 3 (so total of 7 codes: c-3, c-2, c-1, c, c+1, c+2, c+3
// Ensure each corresponding timeslice is returned correctly
@@ -95,7 +96,7 @@ class TwoFactorAuthTest extends TestCase
public function testGetCodeThrowsOnInvalidBase32String1(): void
{
$tfa = new TwoFactorAuth('Test');
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test');
$this->expectException(TwoFactorAuthException::class);
@@ -104,7 +105,7 @@ class TwoFactorAuthTest extends TestCase
public function testGetCodeThrowsOnInvalidBase32String2(): void
{
$tfa = new TwoFactorAuth('Test');
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test');
$this->expectException(TwoFactorAuthException::class);
@@ -124,7 +125,7 @@ class TwoFactorAuthTest extends TestCase
// "In general, you don't want to break any encapsulation for the sake of testing (or as Mom used to say, "don't
// expose your privates!"). Most of the time, you should be able to test a class by exercising its public methods."
// Dave Thomas and Andy Hunt -- "Pragmatic Unit Testing
$tfa = new TwoFactorAuth('Test');
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test');
$method = new ReflectionMethod(TwoFactorAuth::class, 'base32Decode');
@@ -144,7 +145,7 @@ class TwoFactorAuthTest extends TestCase
// This test ensures that strings without the padding-char ('=') are also decoded correctly.
// https://tools.ietf.org/html/rfc4648#page-4:
// "In some circumstances, the use of padding ("=") in base-encoded data is not required or used."
$tfa = new TwoFactorAuth('Test');
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test');
$method = new ReflectionMethod(TwoFactorAuth::class, 'base32Decode');
@@ -162,7 +163,7 @@ class TwoFactorAuthTest extends TestCase
{
//Known test vectors for SHA1: https://tools.ietf.org/html/rfc6238#page-15
$secret = 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ'; //== base32encode('12345678901234567890')
$tfa = new TwoFactorAuth('Test', 8, 30, Algorithm::Sha1);
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 8, 30, Algorithm::Sha1);
$this->assertSame('94287082', $tfa->getCode($secret, 59));
$this->assertSame('07081804', $tfa->getCode($secret, 1111111109));
$this->assertSame('14050471', $tfa->getCode($secret, 1111111111));
@@ -175,7 +176,7 @@ class TwoFactorAuthTest extends TestCase
{
//Known test vectors for SHA256: https://tools.ietf.org/html/rfc6238#page-15
$secret = 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZA'; //== base32encode('12345678901234567890123456789012')
$tfa = new TwoFactorAuth('Test', 8, 30, Algorithm::Sha256);
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 8, 30, Algorithm::Sha256);
$this->assertSame('46119246', $tfa->getCode($secret, 59));
$this->assertSame('68084774', $tfa->getCode($secret, 1111111109));
$this->assertSame('67062674', $tfa->getCode($secret, 1111111111));
@@ -188,7 +189,7 @@ class TwoFactorAuthTest extends TestCase
{
//Known test vectors for SHA512: https://tools.ietf.org/html/rfc6238#page-15
$secret = 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNA'; //== base32encode('1234567890123456789012345678901234567890123456789012345678901234')
$tfa = new TwoFactorAuth('Test', 8, 30, Algorithm::Sha512);
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 8, 30, Algorithm::Sha512);
$this->assertSame('90693936', $tfa->getCode($secret, 59));
$this->assertSame('25091201', $tfa->getCode($secret, 1111111109));
$this->assertSame('99943326', $tfa->getCode($secret, 1111111111));