mirror of
https://github.com/RobThree/TwoFactorAuth.git
synced 2026-09-16 12:26:35 +00:00
Compare commits
53 Commits
v2.0.0-RC1
..
v2.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
| ab93dd41ce | |||
| ec35073c06 | |||
| 13a56018f4 | |||
| 4b6b0601b7 | |||
| 126afe4c25 | |||
| 63f6259db2 | |||
| 137df4dd3c | |||
| 9476751d6b | |||
| 0565d63e6a | |||
| d5aa22a96f | |||
| a6f7735b9f | |||
| 6416e79c3e | |||
| 83611592db | |||
| dd0ecaeed1 | |||
| 241dfec585 | |||
| 63b49ce25c | |||
| e554a9b5e4 | |||
| 9bc454f425 | |||
| c2183e16b3 | |||
| cf84371dad | |||
| 4d91d6ae27 | |||
| d60113a77a | |||
| 3f3327e2a1 | |||
| 8bf962ecee | |||
| b80431341e | |||
| f542788673 | |||
| ab51d16de7 | |||
| d4f6aa1ee6 | |||
| 04ff3e7e3f | |||
| d94ee9c769 | |||
| 955ce522f0 | |||
| e584f0c56a | |||
| 5949a29b89 | |||
| 8bf8800523 | |||
| 2fd60fad5e | |||
| 26413e69f0 | |||
| 3adb3e54e5 | |||
| c45f7a23c9 | |||
| bdfa67d1f6 | |||
| 7675282f45 | |||
| 1ca305ec4a | |||
| aadc32e85e | |||
| 9a1aeb8c1f | |||
| 4c8a88224a | |||
| 9523b35d34 | |||
| cc9400d901 | |||
| dfb5c1a113 | |||
| 098dce6735 | |||
| ae1cc5e8af | |||
| 6e71f43bb1 | |||
| 5968be2cee | |||
| 04e26c9961 | |||
| 8391c5efb0 |
@@ -11,7 +11,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
php-version: ['8.1', '8.2']
|
php-version: ['8.1', '8.2']
|
||||||
endroid-version: ["^4"]
|
endroid-version: ["^3","^4","^5"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@@ -25,7 +25,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: ramsey/composer-install@v2
|
- uses: ramsey/composer-install@v2
|
||||||
|
|
||||||
- run: composer require endroid/qrcode:${{ matrix.endroid-version }}
|
- run: composer require endroid/qrcode:${{ matrix.endroid-version }} -W
|
||||||
|
|
||||||
- run: composer lint-ci
|
- run: composer lint-ci
|
||||||
- run: composer test testsDependency/EndroidQRCodeTest.php
|
- run: composer test testsDependency/EndroidQRCodeTest.php
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ return $config->setRules(array(
|
|||||||
'semicolon_after_instruction' => true,
|
'semicolon_after_instruction' => true,
|
||||||
'short_scalar_cast' => true,
|
'short_scalar_cast' => true,
|
||||||
'simplified_null_return' => true,
|
'simplified_null_return' => true,
|
||||||
'single_blank_line_before_namespace' => true,
|
|
||||||
'single_class_element_per_statement' => true,
|
'single_class_element_per_statement' => true,
|
||||||
'single_line_comment_style' => true,
|
'single_line_comment_style' => true,
|
||||||
'single_quote' => true,
|
'single_quote' => true,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#  PHP library for Two Factor Authentication
|
#  PHP library for Two Factor Authentication
|
||||||
|
|
||||||
[](https://github.com/RobThree/TwoFactorAuth/actions?query=branch%3Amaster) [](https://packagist.org/packages/robthree/twofactorauth) [](LICENSE) [](https://packagist.org/packages/robthree/twofactorauth) [](https://codeclimate.com/github/RobThree/TwoFactorAuth) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=6MB5M2SQLP636 "Keep me off the streets")
|
[](https://github.com/RobThree/TwoFactorAuth/actions?query=branch%3Amaster) [](https://packagist.org/packages/robthree/twofactorauth) [](LICENSE) [](https://packagist.org/packages/robthree/twofactorauth) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=6MB5M2SQLP636 "Keep me off the streets")
|
||||||
|
|
||||||
PHP library for [two-factor (or multi-factor) authentication](http://en.wikipedia.org/wiki/Multi-factor_authentication) using [TOTP](http://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm) and [QR-codes](http://en.wikipedia.org/wiki/QR_code). Inspired by, based on but most importantly an *improvement* on '[PHPGangsta/GoogleAuthenticator](https://github.com/PHPGangsta/GoogleAuthenticator)'. There's a [.Net implementation](https://github.com/RobThree/TwoFactorAuth.Net) of this library as well.
|
PHP library for [two-factor (or multi-factor) authentication](http://en.wikipedia.org/wiki/Multi-factor_authentication) using [TOTP](http://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm) and [QR-codes](http://en.wikipedia.org/wiki/QR_code). Inspired by, based on but most importantly an *improvement* on '[PHPGangsta/GoogleAuthenticator](https://github.com/PHPGangsta/GoogleAuthenticator)'. There's a [.Net implementation](https://github.com/RobThree/TwoFactorAuth.Net) of this library as well.
|
||||||
|
|
||||||
@@ -8,6 +8,13 @@ PHP library for [two-factor (or multi-factor) authentication](http://en.wikipedi
|
|||||||
<img src="https://raw.githubusercontent.com/RobThree/TwoFactorAuth/master/multifactorauthforeveryone.png">
|
<img src="https://raw.githubusercontent.com/RobThree/TwoFactorAuth/master/multifactorauthforeveryone.png">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
## Warning:
|
||||||
|
|
||||||
|
By default, this package uses the `lib/Providers/QRServerProvider.php` as QR code generator. 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 [Endroid](https://robthree.github.io/TwoFactorAuth/qr-codes/endroid.html) or [Bacon](https://robthree.github.io/TwoFactorAuth/qr-codes/bacon.html) providers which generate locally.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
* Requires PHP version >=8.1
|
* Requires PHP version >=8.1
|
||||||
|
|||||||
@@ -7,15 +7,15 @@ title: Optional Configuration
|
|||||||
|
|
||||||
The instance (`new TwoFactorAuth()`) can only be configured by the constructor with the following optional arguments
|
The instance (`new TwoFactorAuth()`) can only be configured by the constructor with the following optional arguments
|
||||||
|
|
||||||
Argument | Default value | Use
|
Argument | Default value | Use
|
||||||
------------------|---------------|-----
|
------------------|-------------------|-----
|
||||||
`$issuer` | `null` | Will be displayed in the users app as the default issuer name when using QR code to import the secret
|
`$issuer` | `null` | Will be displayed in the users app as the default issuer name when using QR code to import the secret
|
||||||
`$digits` | `6` | The number of digits the resulting codes will be
|
`$digits` | `6` | The number of digits the resulting codes will be
|
||||||
`$period` | `30` | The number of seconds a code will be valid
|
`$period` | `30` | The number of seconds a code will be valid
|
||||||
`$algorithm` | `'sha1'` | The algorithm used (one of `sha1`, `sha256`, `sha512`, `md5`)
|
`$algorithm` | `Algorithm::Sha1` | The algorithm used (one of `Algorithm::Sha1`, `Algorithm::Sha256`, `Algorithm::Sha512`, `Algorithm::Md5`)
|
||||||
`$qrcodeprovider` | `null` | QR-code provider
|
`$qrcodeprovider` | `null` | QR-code provider
|
||||||
`$rngprovider` | `null` | Random Number Generator provider
|
`$rngprovider` | `null` | Random Number Generator provider
|
||||||
`$timeprovider` | `null` | Time provider
|
`$timeprovider` | `null` | Time provider
|
||||||
|
|
||||||
**Note:** the default values for `$digits`, `$period`, and `$algorithm` provide the widest variety of support amongst common authenticator apps such as Google Authenticator. If you choose to use different values for these arguments you will likely have to instruct your users to use a specific app which supports your chosen configuration.
|
**Note:** the default values for `$digits`, `$period`, and `$algorithm` provide the widest variety of support amongst common authenticator apps such as Google Authenticator. If you choose to use different values for these arguments you will likely have to instruct your users to use a specific app which supports your chosen configuration.
|
||||||
|
|
||||||
|
|||||||
+9
-10
@@ -20,17 +20,19 @@ You can also specify a size as a third argument which is 200 by default.
|
|||||||
|
|
||||||
## Online Providers
|
## Online Providers
|
||||||
|
|
||||||
[QRServerProvider](qr-codes/qr-server.html) (default)
|
[QRServerProvider](qr-codes/qr-server.md) (default)
|
||||||
|
|
||||||
[ImageChartsQRCodeProvider](qr-codes/image-charts.html)
|
**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.
|
||||||
|
|
||||||
[QRicketProvider](qr-codes/qrickit.html)
|
[ImageChartsQRCodeProvider](qr-codes/image-charts.md)
|
||||||
|
|
||||||
|
[QRicketProvider](qr-codes/qrickit.md)
|
||||||
|
|
||||||
## Offline Providers
|
## Offline Providers
|
||||||
|
|
||||||
[EndroidQrCodeProvider](qr-codes/endroid.html) and EndroidQrCodeWithLogoProvider
|
[EndroidQrCodeProvider](qr-codes/endroid.md) and EndroidQrCodeWithLogoProvider
|
||||||
|
|
||||||
[BaconQRCodeProvider](qr-codes/bacon.html)
|
[BaconQRCodeProvider](qr-codes/bacon.md)
|
||||||
|
|
||||||
**Note:** offline providers may have additional PHP requirements in order to function, you should study what is required before trying to make use of them.
|
**Note:** offline providers may have additional PHP requirements in order to function, you should study what is required before trying to make use of them.
|
||||||
|
|
||||||
@@ -50,11 +52,8 @@ use RobThree\Auth\TwoFactorAuth;
|
|||||||
$qrCodeProvider = new YourChosenProvider();
|
$qrCodeProvider = new YourChosenProvider();
|
||||||
|
|
||||||
$tfa = new TwoFactorAuth(
|
$tfa = new TwoFactorAuth(
|
||||||
null,
|
issuer: "Your Company Or App Name",
|
||||||
6,
|
qrcodeprovider: $qrCodeProvider
|
||||||
30,
|
|
||||||
'sha1',
|
|
||||||
$qrCodeProvider
|
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ use BaconQrCode\Renderer\Image\ImageBackEndInterface;
|
|||||||
use BaconQrCode\Renderer\Image\ImagickImageBackEnd;
|
use BaconQrCode\Renderer\Image\ImagickImageBackEnd;
|
||||||
use BaconQrCode\Renderer\Image\SvgImageBackEnd;
|
use BaconQrCode\Renderer\Image\SvgImageBackEnd;
|
||||||
use BaconQrCode\Renderer\ImageRenderer;
|
use BaconQrCode\Renderer\ImageRenderer;
|
||||||
|
|
||||||
use BaconQrCode\Renderer\RendererStyle\EyeFill;
|
use BaconQrCode\Renderer\RendererStyle\EyeFill;
|
||||||
use BaconQrCode\Renderer\RendererStyle\Fill;
|
use BaconQrCode\Renderer\RendererStyle\Fill;
|
||||||
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
|
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
|
||||||
@@ -23,10 +22,10 @@ class BaconQrCodeProvider implements IQRCodeProvider
|
|||||||
* Ensure we using the latest Bacon QR Code and specify default options
|
* Ensure we using the latest Bacon QR Code and specify default options
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private int $borderWidth = 4,
|
private readonly int $borderWidth = 4,
|
||||||
private string|array $backgroundColour = '#ffffff',
|
private string|array $backgroundColour = '#ffffff',
|
||||||
private string|array $foregroundColour = '#000000',
|
private string|array $foregroundColour = '#000000',
|
||||||
private string $format = 'png',
|
private string $format = 'png',
|
||||||
) {
|
) {
|
||||||
$this->backgroundColour = $this->handleColour($this->backgroundColour);
|
$this->backgroundColour = $this->handleColour($this->backgroundColour);
|
||||||
$this->foregroundColour = $this->handleColour($this->foregroundColour);
|
$this->foregroundColour = $this->handleColour($this->foregroundColour);
|
||||||
@@ -54,20 +53,15 @@ class BaconQrCodeProvider implements IQRCodeProvider
|
|||||||
|
|
||||||
public function getQRCodeImage(string $qrText, int $size): string
|
public function getQRCodeImage(string $qrText, int $size): string
|
||||||
{
|
{
|
||||||
switch ($this->format) {
|
$backend = match ($this->format) {
|
||||||
case 'svg':
|
'svg' => new SvgImageBackEnd(),
|
||||||
$backend = new SvgImageBackEnd();
|
'eps' => new EpsImageBackEnd(),
|
||||||
break;
|
default => new ImagickImageBackEnd($this->format),
|
||||||
case 'eps':
|
};
|
||||||
$backend = new EpsImageBackEnd();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
$backend = new ImagickImageBackEnd($this->format);
|
|
||||||
}
|
|
||||||
|
|
||||||
$output = $this->getQRCodeByBackend($qrText, $size, $backend);
|
$output = $this->getQRCodeByBackend($qrText, $size, $backend);
|
||||||
|
|
||||||
if ($this->format == 'svg') {
|
if ($this->format === 'svg') {
|
||||||
$svg = explode("\n", $output);
|
$svg = explode("\n", $output);
|
||||||
return $svg[1];
|
return $svg[1];
|
||||||
}
|
}
|
||||||
@@ -84,7 +78,7 @@ class BaconQrCodeProvider implements IQRCodeProvider
|
|||||||
$rendererStyleArgs = array($size, $this->borderWidth);
|
$rendererStyleArgs = array($size, $this->borderWidth);
|
||||||
|
|
||||||
if (is_array($this->foregroundColour) && is_array($this->backgroundColour)) {
|
if (is_array($this->foregroundColour) && is_array($this->backgroundColour)) {
|
||||||
$rendererStyleArgs = array_merge($rendererStyleArgs, array(
|
$rendererStyleArgs = array(...$rendererStyleArgs, ...array(
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
Fill::withForegroundColor(
|
Fill::withForegroundColor(
|
||||||
@@ -112,7 +106,7 @@ class BaconQrCodeProvider implements IQRCodeProvider
|
|||||||
private function handleColour(array|string $colour): array|string
|
private function handleColour(array|string $colour): array|string
|
||||||
{
|
{
|
||||||
if (is_string($colour) && $colour[0] == '#') {
|
if (is_string($colour) && $colour[0] == '#') {
|
||||||
$hexToRGB = function ($input) {
|
$hexToRGB = static function ($input) {
|
||||||
// ensure input no longer has a # for more predictable division
|
// ensure input no longer has a # for more predictable division
|
||||||
// PHP 8.1 does not like implicitly casting a float to an int
|
// PHP 8.1 does not like implicitly casting a float to an int
|
||||||
$input = trim($input, '#');
|
$input = trim($input, '#');
|
||||||
@@ -126,7 +120,7 @@ class BaconQrCodeProvider implements IQRCodeProvider
|
|||||||
|
|
||||||
// cope with three character hex reference
|
// cope with three character hex reference
|
||||||
if (strlen($input) == 3) {
|
if (strlen($input) == 3) {
|
||||||
array_walk($split, function (&$character) {
|
array_walk($split, static function (&$character) {
|
||||||
$character = str_repeat($character, 2);
|
$character = str_repeat($character, 2);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,9 +26,12 @@ class EndroidQrCodeProvider implements IQRCodeProvider
|
|||||||
|
|
||||||
protected $endroid4 = false;
|
protected $endroid4 = false;
|
||||||
|
|
||||||
|
protected $endroid5 = false;
|
||||||
|
|
||||||
public function __construct($bgcolor = 'ffffff', $color = '000000', $margin = 0, $errorcorrectionlevel = 'H')
|
public function __construct($bgcolor = 'ffffff', $color = '000000', $margin = 0, $errorcorrectionlevel = 'H')
|
||||||
{
|
{
|
||||||
$this->endroid4 = method_exists(QrCode::class, 'create');
|
$this->endroid4 = method_exists(QrCode::class, 'create');
|
||||||
|
$this->endroid5 = enum_exists(ErrorCorrectionLevel::class);
|
||||||
|
|
||||||
$this->bgcolor = $this->handleColor($bgcolor);
|
$this->bgcolor = $this->handleColor($bgcolor);
|
||||||
$this->color = $this->handleColor($color);
|
$this->color = $this->handleColor($color);
|
||||||
@@ -41,19 +44,19 @@ class EndroidQrCodeProvider implements IQRCodeProvider
|
|||||||
return 'image/png';
|
return 'image/png';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getQRCodeImage(string $qrtext, int $size): string
|
public function getQRCodeImage(string $qrText, int $size): string
|
||||||
{
|
{
|
||||||
if (!$this->endroid4) {
|
if (!$this->endroid4) {
|
||||||
return $this->qrCodeInstance($qrtext, $size)->writeString();
|
return $this->qrCodeInstance($qrText, $size)->writeString();
|
||||||
}
|
}
|
||||||
|
|
||||||
$writer = new PngWriter();
|
$writer = new PngWriter();
|
||||||
return $writer->write($this->qrCodeInstance($qrtext, $size))->getString();
|
return $writer->write($this->qrCodeInstance($qrText, $size))->getString();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function qrCodeInstance(string $qrtext, int $size): QrCode
|
protected function qrCodeInstance(string $qrText, int $size): QrCode
|
||||||
{
|
{
|
||||||
$qrCode = new QrCode($qrtext);
|
$qrCode = new QrCode($qrText);
|
||||||
$qrCode->setSize($size);
|
$qrCode->setSize($size);
|
||||||
|
|
||||||
$qrCode->setErrorCorrectionLevel($this->errorcorrectionlevel);
|
$qrCode->setErrorCorrectionLevel($this->errorcorrectionlevel);
|
||||||
@@ -64,7 +67,7 @@ class EndroidQrCodeProvider implements IQRCodeProvider
|
|||||||
return $qrCode;
|
return $qrCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function handleColor(string $color): Color
|
private function handleColor(string $color): Color|array
|
||||||
{
|
{
|
||||||
$split = str_split($color, 2);
|
$split = str_split($color, 2);
|
||||||
$r = hexdec($split[0]);
|
$r = hexdec($split[0]);
|
||||||
@@ -74,18 +77,34 @@ class EndroidQrCodeProvider implements IQRCodeProvider
|
|||||||
return $this->endroid4 ? new Color($r, $g, $b, 0) : array('r' => $r, 'g' => $g, 'b' => $b, 'a' => 0);
|
return $this->endroid4 ? new Color($r, $g, $b, 0) : array('r' => $r, 'g' => $g, 'b' => $b, 'a' => 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function handleErrorCorrectionLevel(string $level): ErrorCorrectionLevelInterface
|
private function handleErrorCorrectionLevel(string $level): ErrorCorrectionLevelInterface|ErrorCorrectionLevel
|
||||||
{
|
{
|
||||||
switch ($level) {
|
// First check for version 5 (using enums)
|
||||||
case 'L':
|
if ($this->endroid5) {
|
||||||
return $this->endroid4 ? new ErrorCorrectionLevelLow() : ErrorCorrectionLevel::LOW();
|
return match ($level) {
|
||||||
case 'M':
|
'L' => ErrorCorrectionLevel::Low,
|
||||||
return $this->endroid4 ? new ErrorCorrectionLevelMedium() : ErrorCorrectionLevel::MEDIUM();
|
'M' => ErrorCorrectionLevel::Medium,
|
||||||
case 'Q':
|
'Q' => ErrorCorrectionLevel::Quartile,
|
||||||
return $this->endroid4 ? new ErrorCorrectionLevelQuartile() : ErrorCorrectionLevel::QUARTILE();
|
default => ErrorCorrectionLevel::High,
|
||||||
case 'H':
|
};
|
||||||
default:
|
|
||||||
return $this->endroid4 ? new ErrorCorrectionLevelHigh() : ErrorCorrectionLevel::HIGH();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If not check for version 4 (using classes)
|
||||||
|
if ($this->endroid4) {
|
||||||
|
return match ($level) {
|
||||||
|
'L' => new ErrorCorrectionLevelLow(),
|
||||||
|
'M' => new ErrorCorrectionLevelMedium(),
|
||||||
|
'Q' => new ErrorCorrectionLevelQuartile(),
|
||||||
|
default => new ErrorCorrectionLevelHigh(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any other version will be using strings
|
||||||
|
return match ($level) {
|
||||||
|
'L' => ErrorCorrectionLevel::LOW(),
|
||||||
|
'M' => ErrorCorrectionLevel::MEDIUM(),
|
||||||
|
'Q' => ErrorCorrectionLevel::QUARTILE(),
|
||||||
|
default => ErrorCorrectionLevel::HIGH(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,10 +25,10 @@ class EndroidQrCodeWithLogoProvider extends EndroidQrCodeProvider
|
|||||||
$this->logoSize = (array)$size;
|
$this->logoSize = (array)$size;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getQRCodeImage(string $qrtext, int $size): string
|
public function getQRCodeImage(string $qrText, int $size): string
|
||||||
{
|
{
|
||||||
if (!$this->endroid4) {
|
if (!$this->endroid4) {
|
||||||
return $this->qrCodeInstance($qrtext, $size)->writeString();
|
return $this->qrCodeInstance($qrText, $size)->writeString();
|
||||||
}
|
}
|
||||||
|
|
||||||
$logo = null;
|
$logo = null;
|
||||||
@@ -42,12 +42,12 @@ class EndroidQrCodeWithLogoProvider extends EndroidQrCodeProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$writer = new PngWriter();
|
$writer = new PngWriter();
|
||||||
return $writer->write($this->qrCodeInstance($qrtext, $size), $logo)->getString();
|
return $writer->write($this->qrCodeInstance($qrText, $size), $logo)->getString();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function qrCodeInstance(string $qrtext, int $size): QrCode
|
protected function qrCodeInstance(string $qrText, int $size): QrCode
|
||||||
{
|
{
|
||||||
$qrCode = parent::qrCodeInstance($qrtext, $size);
|
$qrCode = parent::qrCodeInstance($qrText, $size);
|
||||||
|
|
||||||
if (!$this->endroid4 && $this->logoPath) {
|
if (!$this->endroid4 && $this->logoPath) {
|
||||||
$qrCode->setLogoPath($this->logoPath);
|
$qrCode->setLogoPath($this->logoPath);
|
||||||
|
|||||||
@@ -16,18 +16,21 @@ class GoogleChartsQrCodeProvider extends BaseHTTPQRCodeProvider
|
|||||||
return 'image/png';
|
return 'image/png';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getQRCodeImage(string $qrtext, int $size): string
|
public function getQRCodeImage(string $qrText, int $size): string
|
||||||
{
|
{
|
||||||
return $this->getContent($this->getUrl($qrtext, $size));
|
return $this->getContent($this->getUrl($qrText, $size));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUrl(string $qrtext, int $size): string
|
public function getUrl(string $qrText, int $size): string
|
||||||
{
|
{
|
||||||
return 'https://chart.googleapis.com/chart'
|
$queryParameters = array(
|
||||||
. '?chs=' . $size . 'x' . $size
|
'chs' => $size . 'x' . $size,
|
||||||
. '&chld=' . urlencode(strtoupper($this->errorcorrectionlevel) . '|' . $this->margin)
|
'chld' => strtoupper($this->errorcorrectionlevel) . '|' . $this->margin,
|
||||||
. '&cht=' . 'qr'
|
'cht' => 'qr',
|
||||||
. '&choe=' . $this->encoding
|
'choe' => $this->encoding,
|
||||||
. '&chl=' . rawurlencode($qrtext);
|
'chl' => $qrText,
|
||||||
|
);
|
||||||
|
|
||||||
|
return 'https://chart.googleapis.com/chart?' . http_build_query($queryParameters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use function preg_match;
|
|||||||
trait HandlesDataUri
|
trait HandlesDataUri
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @return array<string, string>
|
* @return array<string, string>|null
|
||||||
*/
|
*/
|
||||||
private function DecodeDataUri(string $datauri): ?array
|
private function DecodeDataUri(string $datauri): ?array
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ interface IQRCodeProvider
|
|||||||
/**
|
/**
|
||||||
* Generate and return the QR code to embed in a web page
|
* Generate and return the QR code to embed in a web page
|
||||||
*
|
*
|
||||||
* @param string $qrtext the value to encode in the QR code
|
* @param string $qrText the value to encode in the QR code
|
||||||
* @param int $size the desired size of the QR code
|
* @param int $size the desired size of the QR code
|
||||||
*
|
*
|
||||||
* @return string file contents of the QR code
|
* @return string file contents of the QR code
|
||||||
*/
|
*/
|
||||||
public function getQRCodeImage(string $qrtext, int $size): string;
|
public function getQRCodeImage(string $qrText, int $size): string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the appropriate mime type for the QR code
|
* Returns the appropriate mime type for the QR code
|
||||||
|
|||||||
@@ -18,16 +18,20 @@ class ImageChartsQRCodeProvider extends BaseHTTPQRCodeProvider
|
|||||||
return 'image/png';
|
return 'image/png';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getQRCodeImage(string $qrtext, int $size): string
|
public function getQRCodeImage(string $qrText, int $size): string
|
||||||
{
|
{
|
||||||
return $this->getContent($this->getUrl($qrtext, $size));
|
return $this->getContent($this->getUrl($qrText, $size));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUrl(string $qrtext, int $size): string
|
public function getUrl(string $qrText, int $size): string
|
||||||
{
|
{
|
||||||
return 'https://image-charts.com/chart?cht=qr'
|
$queryParameters = array(
|
||||||
. '&chs=' . ceil($size / 2) . 'x' . ceil($size / 2)
|
'cht' => 'qr',
|
||||||
. '&chld=' . $this->errorcorrectionlevel . '|' . $this->margin
|
'chs' => ceil($size / 2) . 'x' . ceil($size / 2),
|
||||||
. '&chl=' . rawurlencode($qrtext);
|
'chld' => $this->errorcorrectionlevel . '|' . $this->margin,
|
||||||
|
'chl' => $qrText,
|
||||||
|
);
|
||||||
|
|
||||||
|
return 'https://image-charts.com/chart?' . http_build_query($queryParameters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,22 +31,25 @@ class QRServerProvider extends BaseHTTPQRCodeProvider
|
|||||||
throw new QRException(sprintf('Unknown MIME-type: %s', $this->format));
|
throw new QRException(sprintf('Unknown MIME-type: %s', $this->format));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getQRCodeImage(string $qrtext, int $size): string
|
public function getQRCodeImage(string $qrText, int $size): string
|
||||||
{
|
{
|
||||||
return $this->getContent($this->getUrl($qrtext, $size));
|
return $this->getContent($this->getUrl($qrText, $size));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUrl(string $qrtext, int $size): string
|
public function getUrl(string $qrText, int $size): string
|
||||||
{
|
{
|
||||||
return 'https://api.qrserver.com/v1/create-qr-code/'
|
$queryParameters = array(
|
||||||
. '?size=' . $size . 'x' . $size
|
'size' => $size . 'x' . $size,
|
||||||
. '&ecc=' . strtoupper($this->errorcorrectionlevel)
|
'ecc' => strtoupper($this->errorcorrectionlevel),
|
||||||
. '&margin=' . $this->margin
|
'margin' => $this->margin,
|
||||||
. '&qzone=' . $this->qzone
|
'qzone' => $this->qzone,
|
||||||
. '&bgcolor=' . $this->decodeColor($this->bgcolor)
|
'bgcolor' => $this->decodeColor($this->bgcolor),
|
||||||
. '&color=' . $this->decodeColor($this->color)
|
'color' => $this->decodeColor($this->color),
|
||||||
. '&format=' . strtolower($this->format)
|
'format' => strtolower($this->format),
|
||||||
. '&data=' . rawurlencode($qrtext);
|
'data' => $qrText,
|
||||||
|
);
|
||||||
|
|
||||||
|
return 'https://api.qrserver.com/v1/create-qr-code/?' . http_build_query($queryParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function decodeColor(string $value): string
|
private function decodeColor(string $value): string
|
||||||
|
|||||||
@@ -27,19 +27,22 @@ class QRicketProvider extends BaseHTTPQRCodeProvider
|
|||||||
throw new QRException(sprintf('Unknown MIME-type: %s', $this->format));
|
throw new QRException(sprintf('Unknown MIME-type: %s', $this->format));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getQRCodeImage(string $qrtext, int $size): string
|
public function getQRCodeImage(string $qrText, int $size): string
|
||||||
{
|
{
|
||||||
return $this->getContent($this->getUrl($qrtext, $size));
|
return $this->getContent($this->getUrl($qrText, $size));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUrl(string $qrtext, int $size): string
|
public function getUrl(string $qrText, int $size): string
|
||||||
{
|
{
|
||||||
return 'http://qrickit.com/api/qr'
|
$queryParameters = array(
|
||||||
. '?qrsize=' . (string) $size
|
'qrsize' => $size,
|
||||||
. '&e=' . strtolower($this->errorcorrectionlevel)
|
'e' => strtolower($this->errorcorrectionlevel),
|
||||||
. '&bgdcolor=' . $this->bgcolor
|
'bgdcolor' => $this->bgcolor,
|
||||||
. '&fgdcolor=' . $this->color
|
'fgdcolor' => $this->color,
|
||||||
. '&t=' . strtolower($this->format)
|
't' => strtolower($this->format),
|
||||||
. '&d=' . rawurlencode($qrtext);
|
'd' => $qrText,
|
||||||
|
);
|
||||||
|
|
||||||
|
return 'http://qrickit.com/api/qr?' . http_build_query($queryParameters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use function in_array;
|
|||||||
|
|
||||||
class HashRNGProvider implements IRNGProvider
|
class HashRNGProvider implements IRNGProvider
|
||||||
{
|
{
|
||||||
public function __construct(private string $algorithm = 'sha256')
|
public function __construct(private readonly string $algorithm = 'sha256')
|
||||||
{
|
{
|
||||||
$algos = array_values(hash_algos());
|
$algos = array_values(hash_algos());
|
||||||
if (!in_array($this->algorithm, $algos, true)) {
|
if (!in_array($this->algorithm, $algos, true)) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace RobThree\Auth\Providers\Rng;
|
|||||||
|
|
||||||
class OpenSSLRNGProvider implements IRNGProvider
|
class OpenSSLRNGProvider implements IRNGProvider
|
||||||
{
|
{
|
||||||
public function __construct(private bool $requirestrong = true)
|
public function __construct(private readonly bool $requirestrong = true)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,21 +12,16 @@ use Exception;
|
|||||||
*/
|
*/
|
||||||
class HttpTimeProvider implements ITimeProvider
|
class HttpTimeProvider implements ITimeProvider
|
||||||
{
|
{
|
||||||
/** @var array<string, mixed> */
|
|
||||||
public array $options;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, mixed> $options
|
* @param array<string, mixed> $options
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public string $url = 'https://google.com',
|
public string $url = 'https://google.com',
|
||||||
public string $expectedtimeformat = 'D, d M Y H:i:s O+',
|
public string $expectedtimeformat = 'D, d M Y H:i:s O+',
|
||||||
array $options = null,
|
public ?array $options = null,
|
||||||
) {
|
) {
|
||||||
$this->url = $url;
|
if ($this->options === null) {
|
||||||
$this->expectedtimeformat = $expectedtimeformat;
|
$this->options = array(
|
||||||
if ($options === null) {
|
|
||||||
$options = array(
|
|
||||||
'http' => array(
|
'http' => array(
|
||||||
'method' => 'HEAD',
|
'method' => 'HEAD',
|
||||||
'follow_location' => false,
|
'follow_location' => false,
|
||||||
@@ -41,7 +36,6 @@ class HttpTimeProvider implements ITimeProvider
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
$this->options = $options;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,7 +44,7 @@ class HttpTimeProvider implements ITimeProvider
|
|||||||
public function getTime()
|
public function getTime()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$context = stream_context_create($this->options);
|
$context = stream_context_create($this->options);
|
||||||
$fd = fopen($this->url, 'rb', false, $context);
|
$fd = fopen($this->url, 'rb', false, $context);
|
||||||
$headers = stream_get_meta_data($fd);
|
$headers = stream_get_meta_data($fd);
|
||||||
fclose($fd);
|
fclose($fd);
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class NTPTimeProvider implements ITimeProvider
|
|||||||
|
|
||||||
// Interpret response
|
// Interpret response
|
||||||
$data = unpack('N12', $recv);
|
$data = unpack('N12', $recv);
|
||||||
$timestamp = (int) sprintf('%u', $data[9]);
|
$timestamp = (int)sprintf('%u', $data[9]);
|
||||||
|
|
||||||
// NTP is number of seconds since 0000 UT on 1 January 1900 Unix time is seconds since 0000 UT on 1 January 1970
|
// NTP is number of seconds since 0000 UT on 1 January 1900 Unix time is seconds since 0000 UT on 1 January 1970
|
||||||
return $timestamp - 2208988800;
|
return $timestamp - 2208988800;
|
||||||
|
|||||||
+21
-31
@@ -28,13 +28,13 @@ class TwoFactorAuth
|
|||||||
private static array $_base32lookup = array();
|
private static array $_base32lookup = array();
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private ?string $issuer = null,
|
private readonly ?string $issuer = null,
|
||||||
private int $digits = 6,
|
private readonly int $digits = 6,
|
||||||
private int $period = 30,
|
private readonly int $period = 30,
|
||||||
private Algorithm $algorithm = Algorithm::Sha1,
|
private readonly Algorithm $algorithm = Algorithm::Sha1,
|
||||||
private ?IQRCodeProvider $qrcodeprovider = null,
|
private ?IQRCodeProvider $qrcodeprovider = null,
|
||||||
private ?IRNGProvider $rngprovider = null,
|
private ?IRNGProvider $rngprovider = null,
|
||||||
private ?ITimeProvider $timeprovider = null
|
private ?ITimeProvider $timeprovider = null
|
||||||
) {
|
) {
|
||||||
if ($this->digits <= 0) {
|
if ($this->digits <= 0) {
|
||||||
throw new TwoFactorAuthException('Digits must be > 0');
|
throw new TwoFactorAuthException('Digits must be > 0');
|
||||||
@@ -54,7 +54,7 @@ class TwoFactorAuth
|
|||||||
public function createSecret(int $bits = 80, bool $requirecryptosecure = true): string
|
public function createSecret(int $bits = 80, bool $requirecryptosecure = true): string
|
||||||
{
|
{
|
||||||
$secret = '';
|
$secret = '';
|
||||||
$bytes = (int) ceil($bits / 5); // We use 5 bits of each byte (since we have a 32-character 'alphabet' / BASE32)
|
$bytes = (int)ceil($bits / 5); // We use 5 bits of each byte (since we have a 32-character 'alphabet' / BASE32)
|
||||||
$rngprovider = $this->getRngProvider();
|
$rngprovider = $this->getRngProvider();
|
||||||
if ($requirecryptosecure && !$rngprovider->isCryptographicallySecure()) {
|
if ($requirecryptosecure && !$rngprovider->isCryptographicallySecure()) {
|
||||||
throw new TwoFactorAuthException('RNG provider is not cryptographically secure');
|
throw new TwoFactorAuthException('RNG provider is not cryptographically secure');
|
||||||
@@ -79,7 +79,7 @@ class TwoFactorAuth
|
|||||||
$value = unpack('N', $hashpart); // Unpack binary value
|
$value = unpack('N', $hashpart); // Unpack binary value
|
||||||
$value = $value[1] & 0x7FFFFFFF; // Drop MSB, keep only 31 bits
|
$value = $value[1] & 0x7FFFFFFF; // Drop MSB, keep only 31 bits
|
||||||
|
|
||||||
return str_pad((string) ($value % 10** $this->digits), $this->digits, '0', STR_PAD_LEFT);
|
return str_pad((string)($value % 10 ** $this->digits), $this->digits, '0', STR_PAD_LEFT);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -123,6 +123,7 @@ class TwoFactorAuth
|
|||||||
/**
|
/**
|
||||||
* Compare default timeprovider with specified timeproviders and ensure the time is within the specified number of seconds (leniency)
|
* Compare default timeprovider with specified timeproviders and ensure the time is within the specified number of seconds (leniency)
|
||||||
* @param array<ITimeProvider> $timeproviders
|
* @param array<ITimeProvider> $timeproviders
|
||||||
|
* @throws TwoFactorAuthException
|
||||||
*/
|
*/
|
||||||
public function ensureCorrectTime(?array $timeproviders = null, int $leniency = 5): void
|
public function ensureCorrectTime(?array $timeproviders = null, int $leniency = 5): void
|
||||||
{
|
{
|
||||||
@@ -157,21 +158,15 @@ class TwoFactorAuth
|
|||||||
return 'otpauth://totp/' . rawurlencode($label)
|
return 'otpauth://totp/' . rawurlencode($label)
|
||||||
. '?secret=' . rawurlencode($secret)
|
. '?secret=' . rawurlencode($secret)
|
||||||
. '&issuer=' . rawurlencode((string)$this->issuer)
|
. '&issuer=' . rawurlencode((string)$this->issuer)
|
||||||
. '&period=' . intval($this->period)
|
. '&period=' . $this->period
|
||||||
. '&algorithm=' . rawurlencode(strtoupper($this->algorithm->value))
|
. '&algorithm=' . rawurlencode(strtoupper($this->algorithm->value))
|
||||||
. '&digits=' . intval($this->digits);
|
. '&digits=' . $this->digits;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws TwoFactorAuthException
|
|
||||||
*/
|
|
||||||
public function getQrCodeProvider(): IQRCodeProvider
|
public function getQrCodeProvider(): IQRCodeProvider
|
||||||
{
|
{
|
||||||
// Set default QR Code provider if none was specified
|
// Set default QR Code provider if none was specified
|
||||||
if (null === $this->qrcodeprovider) {
|
return $this->qrcodeprovider ??= new QRServerProvider();
|
||||||
return $this->qrcodeprovider = new QRServerProvider();
|
|
||||||
}
|
|
||||||
return $this->qrcodeprovider;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -179,7 +174,7 @@ class TwoFactorAuth
|
|||||||
*/
|
*/
|
||||||
public function getRngProvider(): IRNGProvider
|
public function getRngProvider(): IRNGProvider
|
||||||
{
|
{
|
||||||
if (null !== $this->rngprovider) {
|
if ($this->rngprovider !== null) {
|
||||||
return $this->rngprovider;
|
return $this->rngprovider;
|
||||||
}
|
}
|
||||||
if (function_exists('random_bytes')) {
|
if (function_exists('random_bytes')) {
|
||||||
@@ -194,16 +189,10 @@ class TwoFactorAuth
|
|||||||
throw new TwoFactorAuthException('Unable to find a suited RNGProvider');
|
throw new TwoFactorAuthException('Unable to find a suited RNGProvider');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws TwoFactorAuthException
|
|
||||||
*/
|
|
||||||
public function getTimeProvider(): ITimeProvider
|
public function getTimeProvider(): ITimeProvider
|
||||||
{
|
{
|
||||||
// Set default time provider if none was specified
|
// Set default time provider if none was specified
|
||||||
if (null === $this->timeprovider) {
|
return $this->timeprovider ??= new LocalMachineTimeProvider();
|
||||||
return $this->timeprovider = new LocalMachineTimeProvider();
|
|
||||||
}
|
|
||||||
return $this->timeprovider;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -218,7 +207,8 @@ class TwoFactorAuth
|
|||||||
// we don't leak information about the difference of the two strings.
|
// we don't leak information about the difference of the two strings.
|
||||||
if (strlen($safe) === strlen($user)) {
|
if (strlen($safe) === strlen($user)) {
|
||||||
$result = 0;
|
$result = 0;
|
||||||
for ($i = 0; $i < strlen($safe); $i++) {
|
$strlen = strlen($safe);
|
||||||
|
for ($i = 0; $i < $strlen; $i++) {
|
||||||
$result |= (ord($safe[$i]) ^ ord($user[$i]));
|
$result |= (ord($safe[$i]) ^ ord($user[$i]));
|
||||||
}
|
}
|
||||||
return $result === 0;
|
return $result === 0;
|
||||||
@@ -228,21 +218,21 @@ class TwoFactorAuth
|
|||||||
|
|
||||||
private function getTime(?int $time = null): int
|
private function getTime(?int $time = null): int
|
||||||
{
|
{
|
||||||
return ($time === null) ? $this->getTimeProvider()->getTime() : $time;
|
return $time ?? $this->getTimeProvider()->getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getTimeSlice(?int $time = null, int $offset = 0): int
|
private function getTimeSlice(?int $time = null, int $offset = 0): int
|
||||||
{
|
{
|
||||||
return (int) floor($time / $this->period) + ($offset * $this->period);
|
return (int)floor($time / $this->period) + ($offset * $this->period);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function base32Decode(string $value): string
|
private function base32Decode(string $value): string
|
||||||
{
|
{
|
||||||
if (strlen($value) == 0) {
|
if ($value === '') {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preg_match('/[^' . preg_quote(self::$_base32dict) . ']/', $value) !== 0) {
|
if (preg_match('/[^' . preg_quote(self::$_base32dict, '/') . ']/', $value) !== 0) {
|
||||||
throw new TwoFactorAuthException('Invalid base32 string');
|
throw new TwoFactorAuthException('Invalid base32 string');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ class IQRCodeProviderTest extends TestCase
|
|||||||
|
|
||||||
$tfa = new TwoFactorAuth('Test&Issuer', 6, 30, Algorithm::Sha1, $qr);
|
$tfa = new TwoFactorAuth('Test&Issuer', 6, 30, Algorithm::Sha1, $qr);
|
||||||
$data = $this->DecodeDataUri($tfa->getQRCodeImageAsDataUri('Test&Label', 'VMR466AB62ZBOKHE'));
|
$data = $this->DecodeDataUri($tfa->getQRCodeImageAsDataUri('Test&Label', 'VMR466AB62ZBOKHE'));
|
||||||
$this->assertEquals('test/test', $data['mimetype']);
|
$this->assertSame('test/test', $data['mimetype']);
|
||||||
$this->assertEquals('base64', $data['encoding']);
|
$this->assertSame('base64', $data['encoding']);
|
||||||
$this->assertEquals('otpauth://totp/Test%26Label?secret=VMR466AB62ZBOKHE&issuer=Test%26Issuer&period=30&algorithm=SHA1&digits=6@200', $data['data']);
|
$this->assertSame('otpauth://totp/Test%26Label?secret=VMR466AB62ZBOKHE&issuer=Test%26Issuer&period=30&algorithm=SHA1&digits=6@200', $data['data']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testTotpUriIsCorrectNoIssuer(): void
|
public function testTotpUriIsCorrectNoIssuer(): void
|
||||||
@@ -36,9 +36,9 @@ class IQRCodeProviderTest extends TestCase
|
|||||||
|
|
||||||
$tfa = new TwoFactorAuth(null, 6, 30, Algorithm::Sha1, $qr);
|
$tfa = new TwoFactorAuth(null, 6, 30, Algorithm::Sha1, $qr);
|
||||||
$data = $this->DecodeDataUri($tfa->getQRCodeImageAsDataUri('Test&Label', 'VMR466AB62ZBOKHE'));
|
$data = $this->DecodeDataUri($tfa->getQRCodeImageAsDataUri('Test&Label', 'VMR466AB62ZBOKHE'));
|
||||||
$this->assertEquals('test/test', $data['mimetype']);
|
$this->assertSame('test/test', $data['mimetype']);
|
||||||
$this->assertEquals('base64', $data['encoding']);
|
$this->assertSame('base64', $data['encoding']);
|
||||||
$this->assertEquals('otpauth://totp/Test%26Label?secret=VMR466AB62ZBOKHE&issuer=&period=30&algorithm=SHA1&digits=6@200', $data['data']);
|
$this->assertSame('otpauth://totp/Test%26Label?secret=VMR466AB62ZBOKHE&issuer=&period=30&algorithm=SHA1&digits=6@200', $data['data']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetQRCodeImageAsDataUriThrowsOnInvalidSize(): void
|
public function testGetQRCodeImageAsDataUriThrowsOnInvalidSize(): void
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ use RobThree\Auth\Providers\Qr\IQRCodeProvider;
|
|||||||
|
|
||||||
class TestQrProvider implements IQRCodeProvider
|
class TestQrProvider implements IQRCodeProvider
|
||||||
{
|
{
|
||||||
public function getQRCodeImage(string $qrtext, int $size): string
|
public function getQRCodeImage(string $qrText, int $size): string
|
||||||
{
|
{
|
||||||
return $qrtext . '@' . $size;
|
return $qrText . '@' . $size;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMimeType(): string
|
public function getMimeType(): string
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class CSRNGProviderTest extends TestCase
|
|||||||
if (function_exists('random_bytes')) {
|
if (function_exists('random_bytes')) {
|
||||||
$rng = new CSRNGProvider();
|
$rng = new CSRNGProvider();
|
||||||
foreach ($this->rngTestLengths as $l) {
|
foreach ($this->rngTestLengths as $l) {
|
||||||
$this->assertEquals($l, strlen($rng->getRandomBytes($l)));
|
$this->assertSame($l, strlen($rng->getRandomBytes($l)));
|
||||||
}
|
}
|
||||||
$this->assertTrue($rng->isCryptographicallySecure());
|
$this->assertTrue($rng->isCryptographicallySecure());
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class HashRNGProviderTest extends TestCase
|
|||||||
{
|
{
|
||||||
$rng = new HashRNGProvider();
|
$rng = new HashRNGProvider();
|
||||||
foreach ($this->rngTestLengths as $l) {
|
foreach ($this->rngTestLengths as $l) {
|
||||||
$this->assertEquals($l, strlen($rng->getRandomBytes($l)));
|
$this->assertSame($l, strlen($rng->getRandomBytes($l)));
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->assertFalse($rng->isCryptographicallySecure());
|
$this->assertFalse($rng->isCryptographicallySecure());
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class IRNGProviderTest extends TestCase
|
|||||||
$rng = new TestRNGProvider();
|
$rng = new TestRNGProvider();
|
||||||
|
|
||||||
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, $rng);
|
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, $rng);
|
||||||
$this->assertEquals('ABCDEFGHIJKLMNOP', $tfa->createSecret(80, false));
|
$this->assertSame('ABCDEFGHIJKLMNOP', $tfa->createSecret(80, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCreateSecretDoesNotThrowOnSecureRNGProvider(): void
|
public function testCreateSecretDoesNotThrowOnSecureRNGProvider(): void
|
||||||
@@ -34,7 +34,7 @@ class IRNGProviderTest extends TestCase
|
|||||||
$rng = new TestRNGProvider(true);
|
$rng = new TestRNGProvider(true);
|
||||||
|
|
||||||
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, $rng);
|
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, $rng);
|
||||||
$this->assertEquals('ABCDEFGHIJKLMNOP', $tfa->createSecret());
|
$this->assertSame('ABCDEFGHIJKLMNOP', $tfa->createSecret());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCreateSecretGeneratesDesiredAmountOfEntropy(): void
|
public function testCreateSecretGeneratesDesiredAmountOfEntropy(): void
|
||||||
@@ -42,11 +42,11 @@ class IRNGProviderTest extends TestCase
|
|||||||
$rng = new TestRNGProvider(true);
|
$rng = new TestRNGProvider(true);
|
||||||
|
|
||||||
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, $rng);
|
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, $rng);
|
||||||
$this->assertEquals('A', $tfa->createSecret(5));
|
$this->assertSame('A', $tfa->createSecret(5));
|
||||||
$this->assertEquals('AB', $tfa->createSecret(6));
|
$this->assertSame('AB', $tfa->createSecret(6));
|
||||||
$this->assertEquals('ABCDEFGHIJKLMNOPQRSTUVWXYZ', $tfa->createSecret(128));
|
$this->assertSame('ABCDEFGHIJKLMNOPQRSTUVWXYZ', $tfa->createSecret(128));
|
||||||
$this->assertEquals('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', $tfa->createSecret(160));
|
$this->assertSame('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', $tfa->createSecret(160));
|
||||||
$this->assertEquals('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', $tfa->createSecret(320));
|
$this->assertSame('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', $tfa->createSecret(320));
|
||||||
$this->assertEquals('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567ABCDEFGHIJKLMNOPQRSTUVWXYZ234567A', $tfa->createSecret(321));
|
$this->assertSame('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567ABCDEFGHIJKLMNOPQRSTUVWXYZ234567A', $tfa->createSecret(321));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class OpenSSLRNGProviderTest extends TestCase
|
|||||||
{
|
{
|
||||||
$rng = new OpenSSLRNGProvider(true);
|
$rng = new OpenSSLRNGProvider(true);
|
||||||
foreach ($this->rngTestLengths as $l) {
|
foreach ($this->rngTestLengths as $l) {
|
||||||
$this->assertEquals($l, strlen($rng->getRandomBytes($l)));
|
$this->assertSame($l, strlen($rng->getRandomBytes($l)));
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->assertTrue($rng->isCryptographicallySecure());
|
$this->assertTrue($rng->isCryptographicallySecure());
|
||||||
@@ -31,7 +31,7 @@ class OpenSSLRNGProviderTest extends TestCase
|
|||||||
{
|
{
|
||||||
$rng = new OpenSSLRNGProvider(false);
|
$rng = new OpenSSLRNGProvider(false);
|
||||||
foreach ($this->rngTestLengths as $l) {
|
foreach ($this->rngTestLengths as $l) {
|
||||||
$this->assertEquals($l, strlen($rng->getRandomBytes($l)));
|
$this->assertSame($l, strlen($rng->getRandomBytes($l)));
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->assertFalse($rng->isCryptographicallySecure());
|
$this->assertFalse($rng->isCryptographicallySecure());
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use RobThree\Auth\Providers\Rng\IRNGProvider;
|
|||||||
|
|
||||||
class TestRNGProvider implements IRNGProvider
|
class TestRNGProvider implements IRNGProvider
|
||||||
{
|
{
|
||||||
public function __construct(private bool $isSecure = false)
|
public function __construct(private readonly bool $isSecure = false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+57
-57
@@ -7,6 +7,8 @@ namespace Tests;
|
|||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use ReflectionMethod;
|
use ReflectionMethod;
|
||||||
use RobThree\Auth\Algorithm;
|
use RobThree\Auth\Algorithm;
|
||||||
|
use RobThree\Auth\Providers\Time\HttpTimeProvider;
|
||||||
|
use RobThree\Auth\Providers\Time\NTPTimeProvider;
|
||||||
use RobThree\Auth\TwoFactorAuth;
|
use RobThree\Auth\TwoFactorAuth;
|
||||||
use RobThree\Auth\TwoFactorAuthException;
|
use RobThree\Auth\TwoFactorAuthException;
|
||||||
|
|
||||||
@@ -29,19 +31,19 @@ class TwoFactorAuthTest extends TestCase
|
|||||||
public function testGetCodeReturnsCorrectResults(): void
|
public function testGetCodeReturnsCorrectResults(): void
|
||||||
{
|
{
|
||||||
$tfa = new TwoFactorAuth('Test');
|
$tfa = new TwoFactorAuth('Test');
|
||||||
$this->assertEquals('543160', $tfa->getCode('VMR466AB62ZBOKHE', 1426847216));
|
$this->assertSame('543160', $tfa->getCode('VMR466AB62ZBOKHE', 1426847216));
|
||||||
$this->assertEquals('538532', $tfa->getCode('VMR466AB62ZBOKHE', 0));
|
$this->assertSame('538532', $tfa->getCode('VMR466AB62ZBOKHE', 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testEnsureAllTimeProvidersReturnCorrectTime(): void
|
public function testEnsureAllTimeProvidersReturnCorrectTime(): void
|
||||||
{
|
{
|
||||||
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1);
|
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1);
|
||||||
$tfa->ensureCorrectTime(array(
|
$tfa->ensureCorrectTime(array(
|
||||||
new \RobThree\Auth\Providers\Time\NTPTimeProvider(), // Uses pool.ntp.org by default
|
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??
|
//new \RobThree\Auth\Providers\Time\NTPTimeProvider('time.google.com'), // Somehow time.google.com and time.windows.com make travis timeout??
|
||||||
new \RobThree\Auth\Providers\Time\HttpTimeProvider(), // Uses google.com by default
|
new HttpTimeProvider(), // Uses google.com by default
|
||||||
//new \RobThree\Auth\Providers\Time\HttpTimeProvider('https://github.com'), // github.com will periodically report times that are off by more than 5 sec
|
//new \RobThree\Auth\Providers\Time\HttpTimeProvider('https://github.com'), // github.com will periodically report times that are off by more than 5 sec
|
||||||
new \RobThree\Auth\Providers\Time\HttpTimeProvider('https://yahoo.com'),
|
new HttpTimeProvider('https://yahoo.com'),
|
||||||
));
|
));
|
||||||
$this->expectNotToPerformAssertions();
|
$this->expectNotToPerformAssertions();
|
||||||
}
|
}
|
||||||
@@ -50,19 +52,19 @@ class TwoFactorAuthTest extends TestCase
|
|||||||
{
|
{
|
||||||
$tfa = new TwoFactorAuth('Test', 6, 30);
|
$tfa = new TwoFactorAuth('Test', 6, 30);
|
||||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847190));
|
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847190));
|
||||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 0, 1426847190 + 29)); //Test discrepancy
|
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 0, 1426847190 + 29)); //Test discrepancy
|
||||||
$this->assertFalse($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 0, 1426847190 + 30)); //Test discrepancy
|
$this->assertFalse($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 0, 1426847190 + 30)); //Test discrepancy
|
||||||
$this->assertFalse($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 0, 1426847190 - 1)); //Test discrepancy
|
$this->assertFalse($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 0, 1426847190 - 1)); //Test discrepancy
|
||||||
|
|
||||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205 + 0)); //Test discrepancy
|
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205)); //Test discrepancy
|
||||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205 + 35)); //Test discrepancy
|
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205 + 35)); //Test discrepancy
|
||||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205 - 35)); //Test discrepancy
|
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205 - 35)); //Test discrepancy
|
||||||
|
|
||||||
$this->assertFalse($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205 + 65)); //Test discrepancy
|
$this->assertFalse($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205 + 65)); //Test discrepancy
|
||||||
$this->assertFalse($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205 - 65)); //Test discrepancy
|
$this->assertFalse($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205 - 65)); //Test discrepancy
|
||||||
|
|
||||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 2, 1426847205 + 65)); //Test discrepancy
|
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 2, 1426847205 + 65)); //Test discrepancy
|
||||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 2, 1426847205 - 65)); //Test discrepancy
|
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 2, 1426847205 - 65)); //Test discrepancy
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testVerifyCorrectTimeSliceIsReturned(): void
|
public function testVerifyCorrectTimeSliceIsReturned(): void
|
||||||
@@ -72,23 +74,23 @@ class TwoFactorAuthTest extends TestCase
|
|||||||
// We test with discrepancy 3 (so total of 7 codes: c-3, c-2, c-1, c, c+1, c+2, c+3
|
// 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
|
// Ensure each corresponding timeslice is returned correctly
|
||||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '534113', 3, 1426847190, $timeslice1));
|
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '534113', 3, 1426847190, $timeslice1));
|
||||||
$this->assertEquals(47561570, $timeslice1);
|
$this->assertSame(47561570, $timeslice1);
|
||||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '819652', 3, 1426847190, $timeslice2));
|
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '819652', 3, 1426847190, $timeslice2));
|
||||||
$this->assertEquals(47561571, $timeslice2);
|
$this->assertSame(47561571, $timeslice2);
|
||||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '915954', 3, 1426847190, $timeslice3));
|
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '915954', 3, 1426847190, $timeslice3));
|
||||||
$this->assertEquals(47561572, $timeslice3);
|
$this->assertSame(47561572, $timeslice3);
|
||||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 3, 1426847190, $timeslice4));
|
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 3, 1426847190, $timeslice4));
|
||||||
$this->assertEquals(47561573, $timeslice4);
|
$this->assertSame(47561573, $timeslice4);
|
||||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '348401', 3, 1426847190, $timeslice5));
|
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '348401', 3, 1426847190, $timeslice5));
|
||||||
$this->assertEquals(47561574, $timeslice5);
|
$this->assertSame(47561574, $timeslice5);
|
||||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '648525', 3, 1426847190, $timeslice6));
|
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '648525', 3, 1426847190, $timeslice6));
|
||||||
$this->assertEquals(47561575, $timeslice6);
|
$this->assertSame(47561575, $timeslice6);
|
||||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '170645', 3, 1426847190, $timeslice7));
|
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '170645', 3, 1426847190, $timeslice7));
|
||||||
$this->assertEquals(47561576, $timeslice7);
|
$this->assertSame(47561576, $timeslice7);
|
||||||
|
|
||||||
// Incorrect code should return false and a 0 timeslice
|
// Incorrect code should return false and a 0 timeslice
|
||||||
$this->assertFalse($tfa->verifyCode('VMR466AB62ZBOKHE', '111111', 3, 1426847190, $timeslice8));
|
$this->assertFalse($tfa->verifyCode('VMR466AB62ZBOKHE', '111111', 3, 1426847190, $timeslice8));
|
||||||
$this->assertEquals(0, $timeslice8);
|
$this->assertSame(0, $timeslice8);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetCodeThrowsOnInvalidBase32String1(): void
|
public function testGetCodeThrowsOnInvalidBase32String1(): void
|
||||||
@@ -125,16 +127,15 @@ class TwoFactorAuthTest extends TestCase
|
|||||||
$tfa = new TwoFactorAuth('Test');
|
$tfa = new TwoFactorAuth('Test');
|
||||||
|
|
||||||
$method = new ReflectionMethod(TwoFactorAuth::class, 'base32Decode');
|
$method = new ReflectionMethod(TwoFactorAuth::class, 'base32Decode');
|
||||||
$method->setAccessible(true);
|
|
||||||
|
|
||||||
// Test vectors from: https://tools.ietf.org/html/rfc4648#page-12
|
// Test vectors from: https://tools.ietf.org/html/rfc4648#page-12
|
||||||
$this->assertEquals('', $method->invoke($tfa, ''));
|
$this->assertSame('', $method->invoke($tfa, ''));
|
||||||
$this->assertEquals('f', $method->invoke($tfa, 'MY======'));
|
$this->assertSame('f', $method->invoke($tfa, 'MY======'));
|
||||||
$this->assertEquals('fo', $method->invoke($tfa, 'MZXQ===='));
|
$this->assertSame('fo', $method->invoke($tfa, 'MZXQ===='));
|
||||||
$this->assertEquals('foo', $method->invoke($tfa, 'MZXW6==='));
|
$this->assertSame('foo', $method->invoke($tfa, 'MZXW6==='));
|
||||||
$this->assertEquals('foob', $method->invoke($tfa, 'MZXW6YQ='));
|
$this->assertSame('foob', $method->invoke($tfa, 'MZXW6YQ='));
|
||||||
$this->assertEquals('fooba', $method->invoke($tfa, 'MZXW6YTB'));
|
$this->assertSame('fooba', $method->invoke($tfa, 'MZXW6YTB'));
|
||||||
$this->assertEquals('foobar', $method->invoke($tfa, 'MZXW6YTBOI======'));
|
$this->assertSame('foobar', $method->invoke($tfa, 'MZXW6YTBOI======'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testKnownBase32DecodeUnpaddedTestVectors(): void
|
public function testKnownBase32DecodeUnpaddedTestVectors(): void
|
||||||
@@ -146,16 +147,15 @@ class TwoFactorAuthTest extends TestCase
|
|||||||
$tfa = new TwoFactorAuth('Test');
|
$tfa = new TwoFactorAuth('Test');
|
||||||
|
|
||||||
$method = new ReflectionMethod(TwoFactorAuth::class, 'base32Decode');
|
$method = new ReflectionMethod(TwoFactorAuth::class, 'base32Decode');
|
||||||
$method->setAccessible(true);
|
|
||||||
|
|
||||||
// Test vectors from: https://tools.ietf.org/html/rfc4648#page-12
|
// Test vectors from: https://tools.ietf.org/html/rfc4648#page-12
|
||||||
$this->assertEquals('', $method->invoke($tfa, ''));
|
$this->assertSame('', $method->invoke($tfa, ''));
|
||||||
$this->assertEquals('f', $method->invoke($tfa, 'MY'));
|
$this->assertSame('f', $method->invoke($tfa, 'MY'));
|
||||||
$this->assertEquals('fo', $method->invoke($tfa, 'MZXQ'));
|
$this->assertSame('fo', $method->invoke($tfa, 'MZXQ'));
|
||||||
$this->assertEquals('foo', $method->invoke($tfa, 'MZXW6'));
|
$this->assertSame('foo', $method->invoke($tfa, 'MZXW6'));
|
||||||
$this->assertEquals('foob', $method->invoke($tfa, 'MZXW6YQ'));
|
$this->assertSame('foob', $method->invoke($tfa, 'MZXW6YQ'));
|
||||||
$this->assertEquals('fooba', $method->invoke($tfa, 'MZXW6YTB'));
|
$this->assertSame('fooba', $method->invoke($tfa, 'MZXW6YTB'));
|
||||||
$this->assertEquals('foobar', $method->invoke($tfa, 'MZXW6YTBOI'));
|
$this->assertSame('foobar', $method->invoke($tfa, 'MZXW6YTBOI'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testKnownTestVectors_sha1(): void
|
public function testKnownTestVectors_sha1(): void
|
||||||
@@ -163,12 +163,12 @@ class TwoFactorAuthTest extends TestCase
|
|||||||
//Known test vectors for SHA1: https://tools.ietf.org/html/rfc6238#page-15
|
//Known test vectors for SHA1: https://tools.ietf.org/html/rfc6238#page-15
|
||||||
$secret = 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ'; //== base32encode('12345678901234567890')
|
$secret = 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ'; //== base32encode('12345678901234567890')
|
||||||
$tfa = new TwoFactorAuth('Test', 8, 30, Algorithm::Sha1);
|
$tfa = new TwoFactorAuth('Test', 8, 30, Algorithm::Sha1);
|
||||||
$this->assertEquals('94287082', $tfa->getCode($secret, 59));
|
$this->assertSame('94287082', $tfa->getCode($secret, 59));
|
||||||
$this->assertEquals('07081804', $tfa->getCode($secret, 1111111109));
|
$this->assertSame('07081804', $tfa->getCode($secret, 1111111109));
|
||||||
$this->assertEquals('14050471', $tfa->getCode($secret, 1111111111));
|
$this->assertSame('14050471', $tfa->getCode($secret, 1111111111));
|
||||||
$this->assertEquals('89005924', $tfa->getCode($secret, 1234567890));
|
$this->assertSame('89005924', $tfa->getCode($secret, 1234567890));
|
||||||
$this->assertEquals('69279037', $tfa->getCode($secret, 2000000000));
|
$this->assertSame('69279037', $tfa->getCode($secret, 2000000000));
|
||||||
$this->assertEquals('65353130', $tfa->getCode($secret, 20000000000));
|
$this->assertSame('65353130', $tfa->getCode($secret, 20000000000));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testKnownTestVectors_sha256(): void
|
public function testKnownTestVectors_sha256(): void
|
||||||
@@ -176,12 +176,12 @@ class TwoFactorAuthTest extends TestCase
|
|||||||
//Known test vectors for SHA256: https://tools.ietf.org/html/rfc6238#page-15
|
//Known test vectors for SHA256: https://tools.ietf.org/html/rfc6238#page-15
|
||||||
$secret = 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZA'; //== base32encode('12345678901234567890123456789012')
|
$secret = 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZA'; //== base32encode('12345678901234567890123456789012')
|
||||||
$tfa = new TwoFactorAuth('Test', 8, 30, Algorithm::Sha256);
|
$tfa = new TwoFactorAuth('Test', 8, 30, Algorithm::Sha256);
|
||||||
$this->assertEquals('46119246', $tfa->getCode($secret, 59));
|
$this->assertSame('46119246', $tfa->getCode($secret, 59));
|
||||||
$this->assertEquals('68084774', $tfa->getCode($secret, 1111111109));
|
$this->assertSame('68084774', $tfa->getCode($secret, 1111111109));
|
||||||
$this->assertEquals('67062674', $tfa->getCode($secret, 1111111111));
|
$this->assertSame('67062674', $tfa->getCode($secret, 1111111111));
|
||||||
$this->assertEquals('91819424', $tfa->getCode($secret, 1234567890));
|
$this->assertSame('91819424', $tfa->getCode($secret, 1234567890));
|
||||||
$this->assertEquals('90698825', $tfa->getCode($secret, 2000000000));
|
$this->assertSame('90698825', $tfa->getCode($secret, 2000000000));
|
||||||
$this->assertEquals('77737706', $tfa->getCode($secret, 20000000000));
|
$this->assertSame('77737706', $tfa->getCode($secret, 20000000000));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testKnownTestVectors_sha512(): void
|
public function testKnownTestVectors_sha512(): void
|
||||||
@@ -189,11 +189,11 @@ class TwoFactorAuthTest extends TestCase
|
|||||||
//Known test vectors for SHA512: https://tools.ietf.org/html/rfc6238#page-15
|
//Known test vectors for SHA512: https://tools.ietf.org/html/rfc6238#page-15
|
||||||
$secret = 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNA'; //== base32encode('1234567890123456789012345678901234567890123456789012345678901234')
|
$secret = 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNA'; //== base32encode('1234567890123456789012345678901234567890123456789012345678901234')
|
||||||
$tfa = new TwoFactorAuth('Test', 8, 30, Algorithm::Sha512);
|
$tfa = new TwoFactorAuth('Test', 8, 30, Algorithm::Sha512);
|
||||||
$this->assertEquals('90693936', $tfa->getCode($secret, 59));
|
$this->assertSame('90693936', $tfa->getCode($secret, 59));
|
||||||
$this->assertEquals('25091201', $tfa->getCode($secret, 1111111109));
|
$this->assertSame('25091201', $tfa->getCode($secret, 1111111109));
|
||||||
$this->assertEquals('99943326', $tfa->getCode($secret, 1111111111));
|
$this->assertSame('99943326', $tfa->getCode($secret, 1111111111));
|
||||||
$this->assertEquals('93441116', $tfa->getCode($secret, 1234567890));
|
$this->assertSame('93441116', $tfa->getCode($secret, 1234567890));
|
||||||
$this->assertEquals('38618901', $tfa->getCode($secret, 2000000000));
|
$this->assertSame('38618901', $tfa->getCode($secret, 2000000000));
|
||||||
$this->assertEquals('47863826', $tfa->getCode($secret, 20000000000));
|
$this->assertSame('47863826', $tfa->getCode($secret, 20000000000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class BaconQRCodeTest extends TestCase
|
|||||||
$tfa = new TwoFactorAuth('Test&Issuer', 6, 30, Algorithm::Sha1, $qr);
|
$tfa = new TwoFactorAuth('Test&Issuer', 6, 30, Algorithm::Sha1, $qr);
|
||||||
|
|
||||||
$data = $this->DecodeDataUri($tfa->getQRCodeImageAsDataUri('Test&Label', 'VMR466AB62ZBOKHE'));
|
$data = $this->DecodeDataUri($tfa->getQRCodeImageAsDataUri('Test&Label', 'VMR466AB62ZBOKHE'));
|
||||||
$this->assertEquals('image/svg+xml', $data['mimetype']);
|
$this->assertSame('image/svg+xml', $data['mimetype']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testBadTextColour(): void
|
public function testBadTextColour(): void
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ class EndroidQRCodeTest extends TestCase
|
|||||||
$qr = new EndroidQrCodeProvider();
|
$qr = new EndroidQrCodeProvider();
|
||||||
$tfa = new TwoFactorAuth('Test&Issuer', 6, 30, Algorithm::Sha1, $qr);
|
$tfa = new TwoFactorAuth('Test&Issuer', 6, 30, Algorithm::Sha1, $qr);
|
||||||
$data = $this->DecodeDataUri($tfa->getQRCodeImageAsDataUri('Test&Label', 'VMR466AB62ZBOKHE'));
|
$data = $this->DecodeDataUri($tfa->getQRCodeImageAsDataUri('Test&Label', 'VMR466AB62ZBOKHE'));
|
||||||
$this->assertEquals('image/png', $data['mimetype']);
|
$this->assertSame('image/png', $data['mimetype']);
|
||||||
$this->assertEquals('base64', $data['encoding']);
|
$this->assertSame('base64', $data['encoding']);
|
||||||
$this->assertNotEmpty($data['data']);
|
$this->assertNotEmpty($data['data']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user