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

make QR Code Provider a mandatory constructor argument #125

Merged
merged 5 commits into from
Apr 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 16 additions & 11 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,33 @@ title: Getting Started

The best way of making use of this project is by installing it with [composer](https://getcomposer.org/doc/01-basic-usage.md).

```
php composer.phar require robthree/twofactorauth
```

or if you have composer installed globally

```
composer require robthree/twofactorauth
```

## 2. Create an instance

Now you can create an instance for use with your code
`TwoFactorAuth` constructor requires an object able to provide a QR Code image. It is the only mandatory argument. This lets you select your preferred QR Code generator/library.

See [QR code providers documentation](qr-codes.md) for more information about the different possibilites.

Example code:

```php
use RobThree\Auth\TwoFactorAuth;

$tfa = new TwoFactorAuth();
use RobThree\Auth\Providers\Qr\BaconQrCodeProvider; // if using Bacon
use RobThree\Auth\Providers\Qr\EndroidQrCodeProvider; // if using Endroid

// using Bacon
$tfa = new TwoFactorAuth(new BaconQrCodeProvider());
// using Endroid
$tfa = new TwoFactorAuth(new EndroidQrCodeProvider());
// using a custom object implementing IQRCodeProvider interface
$tfa = new TwoFactorAuth(new MyQrCodeProvider());
// using named argument and a variable
$tfa = new TwoFactorAuth(qrcodeprovider: $qrGenerator);
```

**Note:** if you are not using a framework that uses composer, you should [include the composer loader yourself](https://getcomposer.org/doc/01-basic-usage.md#autoloading)

## 3. Shared secrets

When your user is setting up two-factor, or multi-factor, authentication in your project, you can create a secret from the instance.
Expand Down
46 changes: 22 additions & 24 deletions docs/qr-codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ title: QR Codes

An alternative way of communicating the secret to the user is through the use of [QR Codes](http://en.wikipedia.org/wiki/QR_code) which most if not all authenticator mobile apps can scan.

This can avoid accidental typing errors and also pre-set some text values within the users app.
This can avoid accidental typing errors and also pre-set some text values within the two factor authentication mobile application.

You can display the QR Code as a base64 encoded image using the instance as follows, supplying the users name or other public identifier as the first argument

Expand All @@ -16,18 +16,6 @@ You can display the QR Code as a base64 encoded image using the instance as foll

You can also specify a size as a third argument which is 200 by default.

**Note:** by default, the QR code returned by the instance is generated from a third party across the internet. If the third party is encountering problems or is not available from where you have hosted your code, your user will likely experience a delay in seeing the QR code, if it even loads at all. This can be overcome with offline providers configured when you create the instance.

## Online Providers

[QRServerProvider](qr-codes/qr-server.md) (default)

**Warning:** Whilst it is the default, this provider is not suggested for applications where absolute security is needed, because it uses an external service for the QR code generation. You can make use of the included offline providers listed below which generate locally.

[ImageChartsQRCodeProvider](qr-codes/image-charts.md)

[QRicketProvider](qr-codes/qrickit.md)

## Offline Providers

[EndroidQrCodeProvider](qr-codes/endroid.md) and EndroidQrCodeWithLogoProvider
Expand All @@ -38,23 +26,33 @@ You can also specify a size as a third argument which is 200 by default.

## Custom Provider

If you wish to make your own QR Code provider to reference another service or library, it must implement the [IQRCodeProvider interface](https://github.com/RobThree/TwoFactorAuth/blob/master/lib/Providers/Qr/IQRCodeProvider.php).
If you wish to make your own QR Code provider to reference another service or library, it must implement the [IQRCodeProvider interface](../lib/Providers/Qr/IQRCodeProvider.php).

It is recommended to use similar constructor arguments as the included providers to avoid big shifts when trying different providers.

## Using a specific provider

If you do not want to use the default QR code provider, you can specify the one you want to use when you create your instance.
Example:

```php
use RobThree\Auth\TwoFactorAuth;
// using a custom object implementing IQRCodeProvider
$tfa = new TwoFactorAuth(new MyQrCodeProvider());
// using named argument and a variable
$tfa = new TwoFactorAuth(qrcodeprovider: $qrGenerator);
```

## Online Providers

$qrCodeProvider = new YourChosenProvider();
**Warning:** Using an external service for generating QR codes encoding authentication secrets is **not** recommended! You should instead make use of the included offline providers listed above.

$tfa = new TwoFactorAuth(
issuer: "Your Company Or App Name",
qrcodeprovider: $qrCodeProvider
);
```
* Gogr.me: [QRServerProvider](qr-codes/qr-server.md)
* Image Charts: [ImageChartsQRCodeProvider](qr-codes/image-charts.md)
* Qrickit: [QRicketProvider](qr-codes/qrickit.md)
* Google Charts: [GoogleChartsQrCodeProvider](qr-codes/google-charts.md)

As you create a new instance of your provider, you can supply any extra configuration there.
Example:

```php
use RobThree\Auth\TwoFactorAuth;
use RobThree\Auth\Providers\Qr\GoogleChartsQrCodeProvider;
$tfa = new TwoFactorAuth(new GoogleChartsQrCodeProvider());
```
15 changes: 15 additions & 0 deletions docs/qr-codes/google-charts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
layout: post
title: QR GoogleCharts
---

See: https://developers.google.com/chart/infographics/docs/qr_codes

## Optional Configuration

Argument | Default value
------------------------|---------------
`$verifyssl` | `false`
`$errorcorrectionlevel` | `'L'`
`$margin` | `4`
`$encoding` | `'UTF-8'`
2 changes: 1 addition & 1 deletion lib/Providers/Qr/QRicketProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,6 @@ public function getUrl(string $qrText, int $size): string
'd' => $qrText,
);

return 'http://qrickit.com/api/qr?' . http_build_query($queryParameters);
return 'https://qrickit.com/api/qr?' . http_build_query($queryParameters);
}
}
14 changes: 3 additions & 11 deletions lib/TwoFactorAuth.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use function hash_equals;

use RobThree\Auth\Providers\Qr\IQRCodeProvider;
use RobThree\Auth\Providers\Qr\QRServerProvider;
use RobThree\Auth\Providers\Rng\CSRNGProvider;
use RobThree\Auth\Providers\Rng\IRNGProvider;
use RobThree\Auth\Providers\Time\HttpTimeProvider;
Expand All @@ -29,11 +28,11 @@ class TwoFactorAuth
private static array $_base32lookup = array();

public function __construct(
private IQRCodeProvider $qrcodeprovider,
NicolasCARPi marked this conversation as resolved.
Show resolved Hide resolved
private readonly ?string $issuer = null,
private readonly int $digits = 6,
private readonly int $period = 30,
private readonly Algorithm $algorithm = Algorithm::Sha1,
private ?IQRCodeProvider $qrcodeprovider = null,
private ?IRNGProvider $rngprovider = null,
private ?ITimeProvider $timeprovider = null
) {
Expand Down Expand Up @@ -111,11 +110,10 @@ public function getQRCodeImageAsDataUri(string $label, #[SensitiveParameter] str
throw new TwoFactorAuthException('Size must be > 0');
}

$qrcodeprovider = $this->getQrCodeProvider();
return 'data:'
. $qrcodeprovider->getMimeType()
. $this->qrcodeprovider->getMimeType()
. ';base64,'
. base64_encode($qrcodeprovider->getQRCodeImage($this->getQRText($label, $secret), $size));
. base64_encode($this->qrcodeprovider->getQRCodeImage($this->getQRText($label, $secret), $size));
}

/**
Expand Down Expand Up @@ -161,12 +159,6 @@ public function getQRText(string $label, #[SensitiveParameter] string $secret):
. '&digits=' . $this->digits;
}

public function getQrCodeProvider(): IQRCodeProvider
{
// Set default QR Code provider if none was specified
return $this->qrcodeprovider ??= new QRServerProvider();
}

/**
* @throws TwoFactorAuthException
*/
Expand Down
20 changes: 11 additions & 9 deletions tests/Providers/Qr/IQRCodeProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,24 @@
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;

class IQRCodeProviderTest extends TestCase
{
use HandlesDataUri;

public function testTotpUriIsCorrect(): void
protected IQRCodeProvider $qr;

protected function setUp(): void
{
$qr = new TestQrProvider();
$this->qr = new TestQrProvider();
}

$tfa = new TwoFactorAuth('Test&Issuer', 6, 30, Algorithm::Sha1, $qr);
public function testTotpUriIsCorrect(): void
{
$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']);
Expand All @@ -27,14 +33,12 @@ public function testTotpUriIsCorrect(): void

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']);
Expand All @@ -43,9 +47,7 @@ public function testTotpUriIsCorrectNoIssuer(): void

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);

Expand Down
3 changes: 2 additions & 1 deletion tests/Providers/Rng/IRNGProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
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());
}
}
7 changes: 4 additions & 3 deletions tests/Providers/Time/ITimeProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use RobThree\Auth\Algorithm;
use RobThree\Auth\TwoFactorAuth;
use RobThree\Auth\TwoFactorAuthException;
use Tests\Providers\Qr\TestQrProvider;

class ITimeProviderTest extends TestCase
{
Expand All @@ -17,7 +18,7 @@ public function testEnsureCorrectTimeDoesNotThrowForCorrectTime(): void
$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
}

Expand All @@ -26,7 +27,7 @@ public function testEnsureCorrectTimeThrowsOnIncorrectTime(): void
$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);

Expand All @@ -36,7 +37,7 @@ public function testEnsureCorrectTimeThrowsOnIncorrectTime(): void
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
}
}
Loading