Skip to content

Commit f35f2ae

Browse files
authored
make QR Code Provider a mandatory constructor argument PR #125
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 The public function getQRCodeProvider() has been removed. It is provided by the user in the constructor, so it doesn't make a lot of sense to keep a getter around if we're not using it internally.
2 parents cabcf5d + cd27eff commit f35f2ae

File tree

11 files changed

+99
-77
lines changed

11 files changed

+99
-77
lines changed

docs/getting-started.md

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,33 @@ title: Getting Started
77

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

10-
```
11-
php composer.phar require robthree/twofactorauth
12-
```
13-
14-
or if you have composer installed globally
15-
1610
```
1711
composer require robthree/twofactorauth
1812
```
1913

2014
## 2. Create an instance
2115

22-
Now you can create an instance for use with your code
16+
`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.
17+
18+
See [QR code providers documentation](qr-codes.md) for more information about the different possibilites.
19+
20+
Example code:
2321

2422
```php
2523
use RobThree\Auth\TwoFactorAuth;
26-
27-
$tfa = new TwoFactorAuth();
24+
use RobThree\Auth\Providers\Qr\BaconQrCodeProvider; // if using Bacon
25+
use RobThree\Auth\Providers\Qr\EndroidQrCodeProvider; // if using Endroid
26+
27+
// using Bacon
28+
$tfa = new TwoFactorAuth(new BaconQrCodeProvider());
29+
// using Endroid
30+
$tfa = new TwoFactorAuth(new EndroidQrCodeProvider());
31+
// using a custom object implementing IQRCodeProvider interface
32+
$tfa = new TwoFactorAuth(new MyQrCodeProvider());
33+
// using named argument and a variable
34+
$tfa = new TwoFactorAuth(qrcodeprovider: $qrGenerator);
2835
```
2936

30-
**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)
31-
3237
## 3. Shared secrets
3338

3439
When your user is setting up two-factor, or multi-factor, authentication in your project, you can create a secret from the instance.

docs/qr-codes.md

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ title: QR Codes
55

66
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.
77

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

1010
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
1111

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

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

19-
**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.
20-
21-
## Online Providers
22-
23-
[QRServerProvider](qr-codes/qr-server.md) (default)
24-
25-
**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.
26-
27-
[ImageChartsQRCodeProvider](qr-codes/image-charts.md)
28-
29-
[QRicketProvider](qr-codes/qrickit.md)
30-
3119
## Offline Providers
3220

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

3927
## Custom Provider
4028

41-
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).
29+
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).
4230

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

45-
## Using a specific provider
46-
47-
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.
33+
Example:
4834

4935
```php
5036
use RobThree\Auth\TwoFactorAuth;
37+
// using a custom object implementing IQRCodeProvider
38+
$tfa = new TwoFactorAuth(new MyQrCodeProvider());
39+
// using named argument and a variable
40+
$tfa = new TwoFactorAuth(qrcodeprovider: $qrGenerator);
41+
```
42+
43+
## Online Providers
5144

52-
$qrCodeProvider = new YourChosenProvider();
45+
**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.
5346

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

60-
As you create a new instance of your provider, you can supply any extra configuration there.
52+
Example:
53+
54+
```php
55+
use RobThree\Auth\TwoFactorAuth;
56+
use RobThree\Auth\Providers\Qr\GoogleChartsQrCodeProvider;
57+
$tfa = new TwoFactorAuth(new GoogleChartsQrCodeProvider());
58+
```

docs/qr-codes/google-charts.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
layout: post
3+
title: QR GoogleCharts
4+
---
5+
6+
See: https://developers.google.com/chart/infographics/docs/qr_codes
7+
8+
## Optional Configuration
9+
10+
Argument | Default value
11+
------------------------|---------------
12+
`$verifyssl` | `false`
13+
`$errorcorrectionlevel` | `'L'`
14+
`$margin` | `4`
15+
`$encoding` | `'UTF-8'`

lib/Providers/Qr/QRicketProvider.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,6 @@ public function getUrl(string $qrText, int $size): string
4343
'd' => $qrText,
4444
);
4545

46-
return 'http://qrickit.com/api/qr?' . http_build_query($queryParameters);
46+
return 'https://qrickit.com/api/qr?' . http_build_query($queryParameters);
4747
}
4848
}

lib/TwoFactorAuth.php

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
use function hash_equals;
88

99
use RobThree\Auth\Providers\Qr\IQRCodeProvider;
10-
use RobThree\Auth\Providers\Qr\QRServerProvider;
1110
use RobThree\Auth\Providers\Rng\CSRNGProvider;
1211
use RobThree\Auth\Providers\Rng\IRNGProvider;
1312
use RobThree\Auth\Providers\Time\HttpTimeProvider;
@@ -29,11 +28,11 @@ class TwoFactorAuth
2928
private static array $_base32lookup = array();
3029

3130
public function __construct(
31+
private IQRCodeProvider $qrcodeprovider,
3232
private readonly ?string $issuer = null,
3333
private readonly int $digits = 6,
3434
private readonly int $period = 30,
3535
private readonly Algorithm $algorithm = Algorithm::Sha1,
36-
private ?IQRCodeProvider $qrcodeprovider = null,
3736
private ?IRNGProvider $rngprovider = null,
3837
private ?ITimeProvider $timeprovider = null
3938
) {
@@ -111,11 +110,10 @@ public function getQRCodeImageAsDataUri(string $label, #[SensitiveParameter] str
111110
throw new TwoFactorAuthException('Size must be > 0');
112111
}
113112

114-
$qrcodeprovider = $this->getQrCodeProvider();
115113
return 'data:'
116-
. $qrcodeprovider->getMimeType()
114+
. $this->qrcodeprovider->getMimeType()
117115
. ';base64,'
118-
. base64_encode($qrcodeprovider->getQRCodeImage($this->getQRText($label, $secret), $size));
116+
. base64_encode($this->qrcodeprovider->getQRCodeImage($this->getQRText($label, $secret), $size));
119117
}
120118

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

164-
public function getQrCodeProvider(): IQRCodeProvider
165-
{
166-
// Set default QR Code provider if none was specified
167-
return $this->qrcodeprovider ??= new QRServerProvider();
168-
}
169-
170162
/**
171163
* @throws TwoFactorAuthException
172164
*/

tests/Providers/Qr/IQRCodeProviderTest.php

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,24 @@
77
use PHPUnit\Framework\TestCase;
88
use RobThree\Auth\Algorithm;
99
use RobThree\Auth\Providers\Qr\HandlesDataUri;
10+
use RobThree\Auth\Providers\Qr\IQRCodeProvider;
1011
use RobThree\Auth\TwoFactorAuth;
1112
use RobThree\Auth\TwoFactorAuthException;
1213

1314
class IQRCodeProviderTest extends TestCase
1415
{
1516
use HandlesDataUri;
1617

17-
public function testTotpUriIsCorrect(): void
18+
protected IQRCodeProvider $qr;
19+
20+
protected function setUp(): void
1821
{
19-
$qr = new TestQrProvider();
22+
$this->qr = new TestQrProvider();
23+
}
2024

21-
$tfa = new TwoFactorAuth('Test&Issuer', 6, 30, Algorithm::Sha1, $qr);
25+
public function testTotpUriIsCorrect(): void
26+
{
27+
$tfa = new TwoFactorAuth($this->qr, 'Test&Issuer', 6, 30, Algorithm::Sha1);
2228
$data = $this->DecodeDataUri($tfa->getQRCodeImageAsDataUri('Test&Label', 'VMR466AB62ZBOKHE'));
2329
$this->assertSame('test/test', $data['mimetype']);
2430
$this->assertSame('base64', $data['encoding']);
@@ -27,14 +33,12 @@ public function testTotpUriIsCorrect(): void
2733

2834
public function testTotpUriIsCorrectNoIssuer(): void
2935
{
30-
$qr = new TestQrProvider();
31-
3236
/**
3337
* The library specifies the issuer is null by default however in PHP 8.1
3438
* there is a deprecation warning for passing null as a string argument to rawurlencode
3539
*/
3640

37-
$tfa = new TwoFactorAuth(null, 6, 30, Algorithm::Sha1, $qr);
41+
$tfa = new TwoFactorAuth($this->qr, null, 6, 30, Algorithm::Sha1);
3842
$data = $this->DecodeDataUri($tfa->getQRCodeImageAsDataUri('Test&Label', 'VMR466AB62ZBOKHE'));
3943
$this->assertSame('test/test', $data['mimetype']);
4044
$this->assertSame('base64', $data['encoding']);
@@ -43,9 +47,7 @@ public function testTotpUriIsCorrectNoIssuer(): void
4347

4448
public function testGetQRCodeImageAsDataUriThrowsOnInvalidSize(): void
4549
{
46-
$qr = new TestQrProvider();
47-
48-
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, $qr);
50+
$tfa = new TwoFactorAuth($this->qr, 'Test', 6, 30, Algorithm::Sha1);
4951

5052
$this->expectException(TwoFactorAuthException::class);
5153

tests/Providers/Rng/IRNGProviderTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77
use PHPUnit\Framework\TestCase;
88
use RobThree\Auth\Algorithm;
99
use RobThree\Auth\TwoFactorAuth;
10+
use Tests\Providers\Qr\TestQrProvider;
1011

1112
class IRNGProviderTest extends TestCase
1213
{
1314
public function testCreateSecret(): void
1415
{
15-
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, null);
16+
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 6, 30, Algorithm::Sha1, null, null);
1617
$this->assertIsString($tfa->createSecret());
1718
}
1819
}

tests/Providers/Time/ITimeProviderTest.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use RobThree\Auth\Algorithm;
99
use RobThree\Auth\TwoFactorAuth;
1010
use RobThree\Auth\TwoFactorAuthException;
11+
use Tests\Providers\Qr\TestQrProvider;
1112

1213
class ITimeProviderTest extends TestCase
1314
{
@@ -17,7 +18,7 @@ public function testEnsureCorrectTimeDoesNotThrowForCorrectTime(): void
1718
$tpr1 = new TestTimeProvider(123);
1819
$tpr2 = new TestTimeProvider(128);
1920

20-
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, null, $tpr1);
21+
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 6, 30, Algorithm::Sha1, null, $tpr1);
2122
$tfa->ensureCorrectTime(array($tpr2)); // 128 - 123 = 5 => within default leniency
2223
}
2324

@@ -26,7 +27,7 @@ public function testEnsureCorrectTimeThrowsOnIncorrectTime(): void
2627
$tpr1 = new TestTimeProvider(123);
2728
$tpr2 = new TestTimeProvider(124);
2829

29-
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, null, $tpr1);
30+
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 6, 30, Algorithm::Sha1, null, $tpr1);
3031

3132
$this->expectException(TwoFactorAuthException::class);
3233

@@ -36,7 +37,7 @@ public function testEnsureCorrectTimeThrowsOnIncorrectTime(): void
3637
public function testEnsureDefaultTimeProviderReturnsCorrectTime(): void
3738
{
3839
$this->expectNotToPerformAssertions();
39-
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1);
40+
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 6, 30, Algorithm::Sha1);
4041
$tfa->ensureCorrectTime(array(new TestTimeProvider(time())), 1); // Use a leniency of 1, should the time change between both time() calls
4142
}
4243
}

0 commit comments

Comments
 (0)