mirror of
https://github.com/RobThree/TwoFactorAuth.git
synced 2026-08-21 10:31:48 +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:
|
||||
matrix:
|
||||
php-version: ['8.1', '8.2']
|
||||
endroid-version: ["^4"]
|
||||
endroid-version: ["^3","^4","^5"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
|
||||
- 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 test testsDependency/EndroidQRCodeTest.php
|
||||
|
||||
@@ -39,7 +39,6 @@ return $config->setRules(array(
|
||||
'semicolon_after_instruction' => true,
|
||||
'short_scalar_cast' => true,
|
||||
'simplified_null_return' => true,
|
||||
'single_blank_line_before_namespace' => true,
|
||||
'single_class_element_per_statement' => true,
|
||||
'single_line_comment_style' => true,
|
||||
'single_quote' => true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#  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.
|
||||
|
||||
@@ -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">
|
||||
</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
|
||||
|
||||
* 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
|
||||
|
||||
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
|
||||
`$digits` | `6` | The number of digits the resulting codes will be
|
||||
`$period` | `30` | The number of seconds a code will be valid
|
||||
`$algorithm` | `'sha1'` | The algorithm used (one of `sha1`, `sha256`, `sha512`, `md5`)
|
||||
`$qrcodeprovider` | `null` | QR-code provider
|
||||
`$rngprovider` | `null` | Random Number Generator provider
|
||||
`$timeprovider` | `null` | Time provider
|
||||
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
|
||||
`$digits` | `6` | The number of digits the resulting codes will be
|
||||
`$period` | `30` | The number of seconds a code will be valid
|
||||
`$algorithm` | `Algorithm::Sha1` | The algorithm used (one of `Algorithm::Sha1`, `Algorithm::Sha256`, `Algorithm::Sha512`, `Algorithm::Md5`)
|
||||
`$qrcodeprovider` | `null` | QR-code provider
|
||||
`$rngprovider` | `null` | Random Number Generator 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.
|
||||
|
||||
|
||||
+9
-10
@@ -20,17 +20,19 @@ You can also specify a size as a third argument which is 200 by default.
|
||||
|
||||
## 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
|
||||
|
||||
[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.
|
||||
|
||||
@@ -50,11 +52,8 @@ use RobThree\Auth\TwoFactorAuth;
|
||||
$qrCodeProvider = new YourChosenProvider();
|
||||
|
||||
$tfa = new TwoFactorAuth(
|
||||
null,
|
||||
6,
|
||||
30,
|
||||
'sha1',
|
||||
$qrCodeProvider
|
||||
issuer: "Your Company Or App Name",
|
||||
qrcodeprovider: $qrCodeProvider
|
||||
);
|
||||
```
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ use BaconQrCode\Renderer\Image\ImageBackEndInterface;
|
||||
use BaconQrCode\Renderer\Image\ImagickImageBackEnd;
|
||||
use BaconQrCode\Renderer\Image\SvgImageBackEnd;
|
||||
use BaconQrCode\Renderer\ImageRenderer;
|
||||
|
||||
use BaconQrCode\Renderer\RendererStyle\EyeFill;
|
||||
use BaconQrCode\Renderer\RendererStyle\Fill;
|
||||
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
|
||||
*/
|
||||
public function __construct(
|
||||
private int $borderWidth = 4,
|
||||
private readonly int $borderWidth = 4,
|
||||
private string|array $backgroundColour = '#ffffff',
|
||||
private string|array $foregroundColour = '#000000',
|
||||
private string $format = 'png',
|
||||
private string $format = 'png',
|
||||
) {
|
||||
$this->backgroundColour = $this->handleColour($this->backgroundColour);
|
||||
$this->foregroundColour = $this->handleColour($this->foregroundColour);
|
||||
@@ -54,20 +53,15 @@ class BaconQrCodeProvider implements IQRCodeProvider
|
||||
|
||||
public function getQRCodeImage(string $qrText, int $size): string
|
||||
{
|
||||
switch ($this->format) {
|
||||
case 'svg':
|
||||
$backend = new SvgImageBackEnd();
|
||||
break;
|
||||
case 'eps':
|
||||
$backend = new EpsImageBackEnd();
|
||||
break;
|
||||
default:
|
||||
$backend = new ImagickImageBackEnd($this->format);
|
||||
}
|
||||
$backend = match ($this->format) {
|
||||
'svg' => new SvgImageBackEnd(),
|
||||
'eps' => new EpsImageBackEnd(),
|
||||
default => new ImagickImageBackEnd($this->format),
|
||||
};
|
||||
|
||||
$output = $this->getQRCodeByBackend($qrText, $size, $backend);
|
||||
|
||||
if ($this->format == 'svg') {
|
||||
if ($this->format === 'svg') {
|
||||
$svg = explode("\n", $output);
|
||||
return $svg[1];
|
||||
}
|
||||
@@ -84,7 +78,7 @@ class BaconQrCodeProvider implements IQRCodeProvider
|
||||
$rendererStyleArgs = array($size, $this->borderWidth);
|
||||
|
||||
if (is_array($this->foregroundColour) && is_array($this->backgroundColour)) {
|
||||
$rendererStyleArgs = array_merge($rendererStyleArgs, array(
|
||||
$rendererStyleArgs = array(...$rendererStyleArgs, ...array(
|
||||
null,
|
||||
null,
|
||||
Fill::withForegroundColor(
|
||||
@@ -112,7 +106,7 @@ class BaconQrCodeProvider implements IQRCodeProvider
|
||||
private function handleColour(array|string $colour): array|string
|
||||
{
|
||||
if (is_string($colour) && $colour[0] == '#') {
|
||||
$hexToRGB = function ($input) {
|
||||
$hexToRGB = static function ($input) {
|
||||
// ensure input no longer has a # for more predictable division
|
||||
// PHP 8.1 does not like implicitly casting a float to an int
|
||||
$input = trim($input, '#');
|
||||
@@ -126,7 +120,7 @@ class BaconQrCodeProvider implements IQRCodeProvider
|
||||
|
||||
// cope with three character hex reference
|
||||
if (strlen($input) == 3) {
|
||||
array_walk($split, function (&$character) {
|
||||
array_walk($split, static function (&$character) {
|
||||
$character = str_repeat($character, 2);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -26,9 +26,12 @@ class EndroidQrCodeProvider implements IQRCodeProvider
|
||||
|
||||
protected $endroid4 = false;
|
||||
|
||||
protected $endroid5 = false;
|
||||
|
||||
public function __construct($bgcolor = 'ffffff', $color = '000000', $margin = 0, $errorcorrectionlevel = 'H')
|
||||
{
|
||||
$this->endroid4 = method_exists(QrCode::class, 'create');
|
||||
$this->endroid5 = enum_exists(ErrorCorrectionLevel::class);
|
||||
|
||||
$this->bgcolor = $this->handleColor($bgcolor);
|
||||
$this->color = $this->handleColor($color);
|
||||
@@ -41,19 +44,19 @@ class EndroidQrCodeProvider implements IQRCodeProvider
|
||||
return 'image/png';
|
||||
}
|
||||
|
||||
public function getQRCodeImage(string $qrtext, int $size): string
|
||||
public function getQRCodeImage(string $qrText, int $size): string
|
||||
{
|
||||
if (!$this->endroid4) {
|
||||
return $this->qrCodeInstance($qrtext, $size)->writeString();
|
||||
return $this->qrCodeInstance($qrText, $size)->writeString();
|
||||
}
|
||||
|
||||
$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->setErrorCorrectionLevel($this->errorcorrectionlevel);
|
||||
@@ -64,7 +67,7 @@ class EndroidQrCodeProvider implements IQRCodeProvider
|
||||
return $qrCode;
|
||||
}
|
||||
|
||||
private function handleColor(string $color): Color
|
||||
private function handleColor(string $color): Color|array
|
||||
{
|
||||
$split = str_split($color, 2);
|
||||
$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);
|
||||
}
|
||||
|
||||
private function handleErrorCorrectionLevel(string $level): ErrorCorrectionLevelInterface
|
||||
private function handleErrorCorrectionLevel(string $level): ErrorCorrectionLevelInterface|ErrorCorrectionLevel
|
||||
{
|
||||
switch ($level) {
|
||||
case 'L':
|
||||
return $this->endroid4 ? new ErrorCorrectionLevelLow() : ErrorCorrectionLevel::LOW();
|
||||
case 'M':
|
||||
return $this->endroid4 ? new ErrorCorrectionLevelMedium() : ErrorCorrectionLevel::MEDIUM();
|
||||
case 'Q':
|
||||
return $this->endroid4 ? new ErrorCorrectionLevelQuartile() : ErrorCorrectionLevel::QUARTILE();
|
||||
case 'H':
|
||||
default:
|
||||
return $this->endroid4 ? new ErrorCorrectionLevelHigh() : ErrorCorrectionLevel::HIGH();
|
||||
// First check for version 5 (using enums)
|
||||
if ($this->endroid5) {
|
||||
return match ($level) {
|
||||
'L' => ErrorCorrectionLevel::Low,
|
||||
'M' => ErrorCorrectionLevel::Medium,
|
||||
'Q' => ErrorCorrectionLevel::Quartile,
|
||||
default => 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;
|
||||
}
|
||||
|
||||
public function getQRCodeImage(string $qrtext, int $size): string
|
||||
public function getQRCodeImage(string $qrText, int $size): string
|
||||
{
|
||||
if (!$this->endroid4) {
|
||||
return $this->qrCodeInstance($qrtext, $size)->writeString();
|
||||
return $this->qrCodeInstance($qrText, $size)->writeString();
|
||||
}
|
||||
|
||||
$logo = null;
|
||||
@@ -42,12 +42,12 @@ class EndroidQrCodeWithLogoProvider extends EndroidQrCodeProvider
|
||||
}
|
||||
}
|
||||
$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) {
|
||||
$qrCode->setLogoPath($this->logoPath);
|
||||
|
||||
@@ -16,18 +16,21 @@ class GoogleChartsQrCodeProvider extends BaseHTTPQRCodeProvider
|
||||
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'
|
||||
. '?chs=' . $size . 'x' . $size
|
||||
. '&chld=' . urlencode(strtoupper($this->errorcorrectionlevel) . '|' . $this->margin)
|
||||
. '&cht=' . 'qr'
|
||||
. '&choe=' . $this->encoding
|
||||
. '&chl=' . rawurlencode($qrtext);
|
||||
$queryParameters = array(
|
||||
'chs' => $size . 'x' . $size,
|
||||
'chld' => strtoupper($this->errorcorrectionlevel) . '|' . $this->margin,
|
||||
'cht' => 'qr',
|
||||
'choe' => $this->encoding,
|
||||
'chl' => $qrText,
|
||||
);
|
||||
|
||||
return 'https://chart.googleapis.com/chart?' . http_build_query($queryParameters);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use function preg_match;
|
||||
trait HandlesDataUri
|
||||
{
|
||||
/**
|
||||
* @return array<string, string>
|
||||
* @return array<string, string>|null
|
||||
*/
|
||||
private function DecodeDataUri(string $datauri): ?array
|
||||
{
|
||||
|
||||
@@ -9,12 +9,12 @@ interface IQRCodeProvider
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @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
|
||||
|
||||
@@ -18,16 +18,20 @@ class ImageChartsQRCodeProvider extends BaseHTTPQRCodeProvider
|
||||
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'
|
||||
. '&chs=' . ceil($size / 2) . 'x' . ceil($size / 2)
|
||||
. '&chld=' . $this->errorcorrectionlevel . '|' . $this->margin
|
||||
. '&chl=' . rawurlencode($qrtext);
|
||||
$queryParameters = array(
|
||||
'cht' => 'qr',
|
||||
'chs' => ceil($size / 2) . 'x' . ceil($size / 2),
|
||||
'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));
|
||||
}
|
||||
|
||||
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/'
|
||||
. '?size=' . $size . 'x' . $size
|
||||
. '&ecc=' . strtoupper($this->errorcorrectionlevel)
|
||||
. '&margin=' . $this->margin
|
||||
. '&qzone=' . $this->qzone
|
||||
. '&bgcolor=' . $this->decodeColor($this->bgcolor)
|
||||
. '&color=' . $this->decodeColor($this->color)
|
||||
. '&format=' . strtolower($this->format)
|
||||
. '&data=' . rawurlencode($qrtext);
|
||||
$queryParameters = array(
|
||||
'size' => $size . 'x' . $size,
|
||||
'ecc' => strtoupper($this->errorcorrectionlevel),
|
||||
'margin' => $this->margin,
|
||||
'qzone' => $this->qzone,
|
||||
'bgcolor' => $this->decodeColor($this->bgcolor),
|
||||
'color' => $this->decodeColor($this->color),
|
||||
'format' => strtolower($this->format),
|
||||
'data' => $qrText,
|
||||
);
|
||||
|
||||
return 'https://api.qrserver.com/v1/create-qr-code/?' . http_build_query($queryParameters);
|
||||
}
|
||||
|
||||
private function decodeColor(string $value): string
|
||||
|
||||
@@ -27,19 +27,22 @@ class QRicketProvider extends BaseHTTPQRCodeProvider
|
||||
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'
|
||||
. '?qrsize=' . (string) $size
|
||||
. '&e=' . strtolower($this->errorcorrectionlevel)
|
||||
. '&bgdcolor=' . $this->bgcolor
|
||||
. '&fgdcolor=' . $this->color
|
||||
. '&t=' . strtolower($this->format)
|
||||
. '&d=' . rawurlencode($qrtext);
|
||||
$queryParameters = array(
|
||||
'qrsize' => $size,
|
||||
'e' => strtolower($this->errorcorrectionlevel),
|
||||
'bgdcolor' => $this->bgcolor,
|
||||
'fgdcolor' => $this->color,
|
||||
't' => strtolower($this->format),
|
||||
'd' => $qrText,
|
||||
);
|
||||
|
||||
return 'http://qrickit.com/api/qr?' . http_build_query($queryParameters);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use function in_array;
|
||||
|
||||
class HashRNGProvider implements IRNGProvider
|
||||
{
|
||||
public function __construct(private string $algorithm = 'sha256')
|
||||
public function __construct(private readonly string $algorithm = 'sha256')
|
||||
{
|
||||
$algos = array_values(hash_algos());
|
||||
if (!in_array($this->algorithm, $algos, true)) {
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace RobThree\Auth\Providers\Rng;
|
||||
|
||||
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
|
||||
{
|
||||
/** @var array<string, mixed> */
|
||||
public array $options;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $options
|
||||
*/
|
||||
public function __construct(
|
||||
public string $url = 'https://google.com',
|
||||
public string $expectedtimeformat = 'D, d M Y H:i:s O+',
|
||||
array $options = null,
|
||||
public ?array $options = null,
|
||||
) {
|
||||
$this->url = $url;
|
||||
$this->expectedtimeformat = $expectedtimeformat;
|
||||
if ($options === null) {
|
||||
$options = array(
|
||||
if ($this->options === null) {
|
||||
$this->options = array(
|
||||
'http' => array(
|
||||
'method' => 'HEAD',
|
||||
'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()
|
||||
{
|
||||
try {
|
||||
$context = stream_context_create($this->options);
|
||||
$context = stream_context_create($this->options);
|
||||
$fd = fopen($this->url, 'rb', false, $context);
|
||||
$headers = stream_get_meta_data($fd);
|
||||
fclose($fd);
|
||||
|
||||
@@ -47,7 +47,7 @@ class NTPTimeProvider implements ITimeProvider
|
||||
|
||||
// Interpret response
|
||||
$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
|
||||
return $timestamp - 2208988800;
|
||||
|
||||
+21
-31
@@ -28,13 +28,13 @@ class TwoFactorAuth
|
||||
private static array $_base32lookup = array();
|
||||
|
||||
public function __construct(
|
||||
private ?string $issuer = null,
|
||||
private int $digits = 6,
|
||||
private int $period = 30,
|
||||
private Algorithm $algorithm = Algorithm::Sha1,
|
||||
private ?IQRCodeProvider $qrcodeprovider = null,
|
||||
private ?IRNGProvider $rngprovider = null,
|
||||
private ?ITimeProvider $timeprovider = null
|
||||
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
|
||||
) {
|
||||
if ($this->digits <= 0) {
|
||||
throw new TwoFactorAuthException('Digits must be > 0');
|
||||
@@ -54,7 +54,7 @@ class TwoFactorAuth
|
||||
public function createSecret(int $bits = 80, bool $requirecryptosecure = true): string
|
||||
{
|
||||
$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();
|
||||
if ($requirecryptosecure && !$rngprovider->isCryptographicallySecure()) {
|
||||
throw new TwoFactorAuthException('RNG provider is not cryptographically secure');
|
||||
@@ -79,7 +79,7 @@ class TwoFactorAuth
|
||||
$value = unpack('N', $hashpart); // Unpack binary value
|
||||
$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)
|
||||
* @param array<ITimeProvider> $timeproviders
|
||||
* @throws TwoFactorAuthException
|
||||
*/
|
||||
public function ensureCorrectTime(?array $timeproviders = null, int $leniency = 5): void
|
||||
{
|
||||
@@ -157,21 +158,15 @@ class TwoFactorAuth
|
||||
return 'otpauth://totp/' . rawurlencode($label)
|
||||
. '?secret=' . rawurlencode($secret)
|
||||
. '&issuer=' . rawurlencode((string)$this->issuer)
|
||||
. '&period=' . intval($this->period)
|
||||
. '&period=' . $this->period
|
||||
. '&algorithm=' . rawurlencode(strtoupper($this->algorithm->value))
|
||||
. '&digits=' . intval($this->digits);
|
||||
. '&digits=' . $this->digits;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws TwoFactorAuthException
|
||||
*/
|
||||
public function getQrCodeProvider(): IQRCodeProvider
|
||||
{
|
||||
// Set default QR Code provider if none was specified
|
||||
if (null === $this->qrcodeprovider) {
|
||||
return $this->qrcodeprovider = new QRServerProvider();
|
||||
}
|
||||
return $this->qrcodeprovider;
|
||||
return $this->qrcodeprovider ??= new QRServerProvider();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -179,7 +174,7 @@ class TwoFactorAuth
|
||||
*/
|
||||
public function getRngProvider(): IRNGProvider
|
||||
{
|
||||
if (null !== $this->rngprovider) {
|
||||
if ($this->rngprovider !== null) {
|
||||
return $this->rngprovider;
|
||||
}
|
||||
if (function_exists('random_bytes')) {
|
||||
@@ -194,16 +189,10 @@ class TwoFactorAuth
|
||||
throw new TwoFactorAuthException('Unable to find a suited RNGProvider');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws TwoFactorAuthException
|
||||
*/
|
||||
public function getTimeProvider(): ITimeProvider
|
||||
{
|
||||
// Set default time provider if none was specified
|
||||
if (null === $this->timeprovider) {
|
||||
return $this->timeprovider = new LocalMachineTimeProvider();
|
||||
}
|
||||
return $this->timeprovider;
|
||||
return $this->timeprovider ??= new LocalMachineTimeProvider();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -218,7 +207,8 @@ class TwoFactorAuth
|
||||
// we don't leak information about the difference of the two strings.
|
||||
if (strlen($safe) === strlen($user)) {
|
||||
$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]));
|
||||
}
|
||||
return $result === 0;
|
||||
@@ -228,21 +218,21 @@ class TwoFactorAuth
|
||||
|
||||
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
|
||||
{
|
||||
return (int) floor($time / $this->period) + ($offset * $this->period);
|
||||
return (int)floor($time / $this->period) + ($offset * $this->period);
|
||||
}
|
||||
|
||||
private function base32Decode(string $value): string
|
||||
{
|
||||
if (strlen($value) == 0) {
|
||||
if ($value === '') {
|
||||
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');
|
||||
}
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ class IQRCodeProviderTest extends TestCase
|
||||
|
||||
$tfa = new TwoFactorAuth('Test&Issuer', 6, 30, Algorithm::Sha1, $qr);
|
||||
$data = $this->DecodeDataUri($tfa->getQRCodeImageAsDataUri('Test&Label', 'VMR466AB62ZBOKHE'));
|
||||
$this->assertEquals('test/test', $data['mimetype']);
|
||||
$this->assertEquals('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('test/test', $data['mimetype']);
|
||||
$this->assertSame('base64', $data['encoding']);
|
||||
$this->assertSame('otpauth://totp/Test%26Label?secret=VMR466AB62ZBOKHE&issuer=Test%26Issuer&period=30&algorithm=SHA1&digits=6@200', $data['data']);
|
||||
}
|
||||
|
||||
public function testTotpUriIsCorrectNoIssuer(): void
|
||||
@@ -36,9 +36,9 @@ class IQRCodeProviderTest extends TestCase
|
||||
|
||||
$tfa = new TwoFactorAuth(null, 6, 30, Algorithm::Sha1, $qr);
|
||||
$data = $this->DecodeDataUri($tfa->getQRCodeImageAsDataUri('Test&Label', 'VMR466AB62ZBOKHE'));
|
||||
$this->assertEquals('test/test', $data['mimetype']);
|
||||
$this->assertEquals('base64', $data['encoding']);
|
||||
$this->assertEquals('otpauth://totp/Test%26Label?secret=VMR466AB62ZBOKHE&issuer=&period=30&algorithm=SHA1&digits=6@200', $data['data']);
|
||||
$this->assertSame('test/test', $data['mimetype']);
|
||||
$this->assertSame('base64', $data['encoding']);
|
||||
$this->assertSame('otpauth://totp/Test%26Label?secret=VMR466AB62ZBOKHE&issuer=&period=30&algorithm=SHA1&digits=6@200', $data['data']);
|
||||
}
|
||||
|
||||
public function testGetQRCodeImageAsDataUriThrowsOnInvalidSize(): void
|
||||
|
||||
@@ -8,9 +8,9 @@ use RobThree\Auth\Providers\Qr\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
|
||||
|
||||
@@ -19,7 +19,7 @@ class CSRNGProviderTest extends TestCase
|
||||
if (function_exists('random_bytes')) {
|
||||
$rng = new CSRNGProvider();
|
||||
foreach ($this->rngTestLengths as $l) {
|
||||
$this->assertEquals($l, strlen($rng->getRandomBytes($l)));
|
||||
$this->assertSame($l, strlen($rng->getRandomBytes($l)));
|
||||
}
|
||||
$this->assertTrue($rng->isCryptographicallySecure());
|
||||
} else {
|
||||
|
||||
@@ -18,7 +18,7 @@ class HashRNGProviderTest extends TestCase
|
||||
{
|
||||
$rng = new HashRNGProvider();
|
||||
foreach ($this->rngTestLengths as $l) {
|
||||
$this->assertEquals($l, strlen($rng->getRandomBytes($l)));
|
||||
$this->assertSame($l, strlen($rng->getRandomBytes($l)));
|
||||
}
|
||||
|
||||
$this->assertFalse($rng->isCryptographicallySecure());
|
||||
|
||||
@@ -26,7 +26,7 @@ class IRNGProviderTest extends TestCase
|
||||
$rng = new TestRNGProvider();
|
||||
|
||||
$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
|
||||
@@ -34,7 +34,7 @@ class IRNGProviderTest extends TestCase
|
||||
$rng = new TestRNGProvider(true);
|
||||
|
||||
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, $rng);
|
||||
$this->assertEquals('ABCDEFGHIJKLMNOP', $tfa->createSecret());
|
||||
$this->assertSame('ABCDEFGHIJKLMNOP', $tfa->createSecret());
|
||||
}
|
||||
|
||||
public function testCreateSecretGeneratesDesiredAmountOfEntropy(): void
|
||||
@@ -42,11 +42,11 @@ class IRNGProviderTest extends TestCase
|
||||
$rng = new TestRNGProvider(true);
|
||||
|
||||
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, $rng);
|
||||
$this->assertEquals('A', $tfa->createSecret(5));
|
||||
$this->assertEquals('AB', $tfa->createSecret(6));
|
||||
$this->assertEquals('ABCDEFGHIJKLMNOPQRSTUVWXYZ', $tfa->createSecret(128));
|
||||
$this->assertEquals('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', $tfa->createSecret(160));
|
||||
$this->assertEquals('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', $tfa->createSecret(320));
|
||||
$this->assertEquals('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567ABCDEFGHIJKLMNOPQRSTUVWXYZ234567A', $tfa->createSecret(321));
|
||||
$this->assertSame('A', $tfa->createSecret(5));
|
||||
$this->assertSame('AB', $tfa->createSecret(6));
|
||||
$this->assertSame('ABCDEFGHIJKLMNOPQRSTUVWXYZ', $tfa->createSecret(128));
|
||||
$this->assertSame('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', $tfa->createSecret(160));
|
||||
$this->assertSame('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', $tfa->createSecret(320));
|
||||
$this->assertSame('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567ABCDEFGHIJKLMNOPQRSTUVWXYZ234567A', $tfa->createSecret(321));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ class OpenSSLRNGProviderTest extends TestCase
|
||||
{
|
||||
$rng = new OpenSSLRNGProvider(true);
|
||||
foreach ($this->rngTestLengths as $l) {
|
||||
$this->assertEquals($l, strlen($rng->getRandomBytes($l)));
|
||||
$this->assertSame($l, strlen($rng->getRandomBytes($l)));
|
||||
}
|
||||
|
||||
$this->assertTrue($rng->isCryptographicallySecure());
|
||||
@@ -31,7 +31,7 @@ class OpenSSLRNGProviderTest extends TestCase
|
||||
{
|
||||
$rng = new OpenSSLRNGProvider(false);
|
||||
foreach ($this->rngTestLengths as $l) {
|
||||
$this->assertEquals($l, strlen($rng->getRandomBytes($l)));
|
||||
$this->assertSame($l, strlen($rng->getRandomBytes($l)));
|
||||
}
|
||||
|
||||
$this->assertFalse($rng->isCryptographicallySecure());
|
||||
|
||||
@@ -8,7 +8,7 @@ use RobThree\Auth\Providers\Rng\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 ReflectionMethod;
|
||||
use RobThree\Auth\Algorithm;
|
||||
use RobThree\Auth\Providers\Time\HttpTimeProvider;
|
||||
use RobThree\Auth\Providers\Time\NTPTimeProvider;
|
||||
use RobThree\Auth\TwoFactorAuth;
|
||||
use RobThree\Auth\TwoFactorAuthException;
|
||||
|
||||
@@ -29,19 +31,19 @@ class TwoFactorAuthTest extends TestCase
|
||||
public function testGetCodeReturnsCorrectResults(): void
|
||||
{
|
||||
$tfa = new TwoFactorAuth('Test');
|
||||
$this->assertEquals('543160', $tfa->getCode('VMR466AB62ZBOKHE', 1426847216));
|
||||
$this->assertEquals('538532', $tfa->getCode('VMR466AB62ZBOKHE', 0));
|
||||
$this->assertSame('543160', $tfa->getCode('VMR466AB62ZBOKHE', 1426847216));
|
||||
$this->assertSame('538532', $tfa->getCode('VMR466AB62ZBOKHE', 0));
|
||||
}
|
||||
|
||||
public function testEnsureAllTimeProvidersReturnCorrectTime(): void
|
||||
{
|
||||
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1);
|
||||
$tfa->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\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://yahoo.com'),
|
||||
new HttpTimeProvider('https://yahoo.com'),
|
||||
));
|
||||
$this->expectNotToPerformAssertions();
|
||||
}
|
||||
@@ -50,19 +52,19 @@ class TwoFactorAuthTest extends TestCase
|
||||
{
|
||||
$tfa = new TwoFactorAuth('Test', 6, 30);
|
||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847190));
|
||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 0, 1426847190 + 29)); //Test discrepancy
|
||||
$this->assertFalse($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 0, 1426847190 + 30)); //Test discrepancy
|
||||
$this->assertFalse($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 0, 1426847190 - 1)); //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 - 1)); //Test discrepancy
|
||||
|
||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205 + 0)); //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)); //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
|
||||
@@ -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
|
||||
// Ensure each corresponding timeslice is returned correctly
|
||||
$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->assertEquals(47561571, $timeslice2);
|
||||
$this->assertSame(47561571, $timeslice2);
|
||||
$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->assertEquals(47561573, $timeslice4);
|
||||
$this->assertSame(47561573, $timeslice4);
|
||||
$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->assertEquals(47561575, $timeslice6);
|
||||
$this->assertSame(47561575, $timeslice6);
|
||||
$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
|
||||
$this->assertFalse($tfa->verifyCode('VMR466AB62ZBOKHE', '111111', 3, 1426847190, $timeslice8));
|
||||
$this->assertEquals(0, $timeslice8);
|
||||
$this->assertSame(0, $timeslice8);
|
||||
}
|
||||
|
||||
public function testGetCodeThrowsOnInvalidBase32String1(): void
|
||||
@@ -125,16 +127,15 @@ class TwoFactorAuthTest extends TestCase
|
||||
$tfa = new TwoFactorAuth('Test');
|
||||
|
||||
$method = new ReflectionMethod(TwoFactorAuth::class, 'base32Decode');
|
||||
$method->setAccessible(true);
|
||||
|
||||
// Test vectors from: https://tools.ietf.org/html/rfc4648#page-12
|
||||
$this->assertEquals('', $method->invoke($tfa, ''));
|
||||
$this->assertEquals('f', $method->invoke($tfa, 'MY======'));
|
||||
$this->assertEquals('fo', $method->invoke($tfa, 'MZXQ===='));
|
||||
$this->assertEquals('foo', $method->invoke($tfa, 'MZXW6==='));
|
||||
$this->assertEquals('foob', $method->invoke($tfa, 'MZXW6YQ='));
|
||||
$this->assertEquals('fooba', $method->invoke($tfa, 'MZXW6YTB'));
|
||||
$this->assertEquals('foobar', $method->invoke($tfa, 'MZXW6YTBOI======'));
|
||||
$this->assertSame('', $method->invoke($tfa, ''));
|
||||
$this->assertSame('f', $method->invoke($tfa, 'MY======'));
|
||||
$this->assertSame('fo', $method->invoke($tfa, 'MZXQ===='));
|
||||
$this->assertSame('foo', $method->invoke($tfa, 'MZXW6==='));
|
||||
$this->assertSame('foob', $method->invoke($tfa, 'MZXW6YQ='));
|
||||
$this->assertSame('fooba', $method->invoke($tfa, 'MZXW6YTB'));
|
||||
$this->assertSame('foobar', $method->invoke($tfa, 'MZXW6YTBOI======'));
|
||||
}
|
||||
|
||||
public function testKnownBase32DecodeUnpaddedTestVectors(): void
|
||||
@@ -146,16 +147,15 @@ class TwoFactorAuthTest extends TestCase
|
||||
$tfa = new TwoFactorAuth('Test');
|
||||
|
||||
$method = new ReflectionMethod(TwoFactorAuth::class, 'base32Decode');
|
||||
$method->setAccessible(true);
|
||||
|
||||
// Test vectors from: https://tools.ietf.org/html/rfc4648#page-12
|
||||
$this->assertEquals('', $method->invoke($tfa, ''));
|
||||
$this->assertEquals('f', $method->invoke($tfa, 'MY'));
|
||||
$this->assertEquals('fo', $method->invoke($tfa, 'MZXQ'));
|
||||
$this->assertEquals('foo', $method->invoke($tfa, 'MZXW6'));
|
||||
$this->assertEquals('foob', $method->invoke($tfa, 'MZXW6YQ'));
|
||||
$this->assertEquals('fooba', $method->invoke($tfa, 'MZXW6YTB'));
|
||||
$this->assertEquals('foobar', $method->invoke($tfa, 'MZXW6YTBOI'));
|
||||
$this->assertSame('', $method->invoke($tfa, ''));
|
||||
$this->assertSame('f', $method->invoke($tfa, 'MY'));
|
||||
$this->assertSame('fo', $method->invoke($tfa, 'MZXQ'));
|
||||
$this->assertSame('foo', $method->invoke($tfa, 'MZXW6'));
|
||||
$this->assertSame('foob', $method->invoke($tfa, 'MZXW6YQ'));
|
||||
$this->assertSame('fooba', $method->invoke($tfa, 'MZXW6YTB'));
|
||||
$this->assertSame('foobar', $method->invoke($tfa, 'MZXW6YTBOI'));
|
||||
}
|
||||
|
||||
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
|
||||
$secret = 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ'; //== base32encode('12345678901234567890')
|
||||
$tfa = new TwoFactorAuth('Test', 8, 30, Algorithm::Sha1);
|
||||
$this->assertEquals('94287082', $tfa->getCode($secret, 59));
|
||||
$this->assertEquals('07081804', $tfa->getCode($secret, 1111111109));
|
||||
$this->assertEquals('14050471', $tfa->getCode($secret, 1111111111));
|
||||
$this->assertEquals('89005924', $tfa->getCode($secret, 1234567890));
|
||||
$this->assertEquals('69279037', $tfa->getCode($secret, 2000000000));
|
||||
$this->assertEquals('65353130', $tfa->getCode($secret, 20000000000));
|
||||
$this->assertSame('94287082', $tfa->getCode($secret, 59));
|
||||
$this->assertSame('07081804', $tfa->getCode($secret, 1111111109));
|
||||
$this->assertSame('14050471', $tfa->getCode($secret, 1111111111));
|
||||
$this->assertSame('89005924', $tfa->getCode($secret, 1234567890));
|
||||
$this->assertSame('69279037', $tfa->getCode($secret, 2000000000));
|
||||
$this->assertSame('65353130', $tfa->getCode($secret, 20000000000));
|
||||
}
|
||||
|
||||
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
|
||||
$secret = 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZA'; //== base32encode('12345678901234567890123456789012')
|
||||
$tfa = new TwoFactorAuth('Test', 8, 30, Algorithm::Sha256);
|
||||
$this->assertEquals('46119246', $tfa->getCode($secret, 59));
|
||||
$this->assertEquals('68084774', $tfa->getCode($secret, 1111111109));
|
||||
$this->assertEquals('67062674', $tfa->getCode($secret, 1111111111));
|
||||
$this->assertEquals('91819424', $tfa->getCode($secret, 1234567890));
|
||||
$this->assertEquals('90698825', $tfa->getCode($secret, 2000000000));
|
||||
$this->assertEquals('77737706', $tfa->getCode($secret, 20000000000));
|
||||
$this->assertSame('46119246', $tfa->getCode($secret, 59));
|
||||
$this->assertSame('68084774', $tfa->getCode($secret, 1111111109));
|
||||
$this->assertSame('67062674', $tfa->getCode($secret, 1111111111));
|
||||
$this->assertSame('91819424', $tfa->getCode($secret, 1234567890));
|
||||
$this->assertSame('90698825', $tfa->getCode($secret, 2000000000));
|
||||
$this->assertSame('77737706', $tfa->getCode($secret, 20000000000));
|
||||
}
|
||||
|
||||
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
|
||||
$secret = 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNA'; //== base32encode('1234567890123456789012345678901234567890123456789012345678901234')
|
||||
$tfa = new TwoFactorAuth('Test', 8, 30, Algorithm::Sha512);
|
||||
$this->assertEquals('90693936', $tfa->getCode($secret, 59));
|
||||
$this->assertEquals('25091201', $tfa->getCode($secret, 1111111109));
|
||||
$this->assertEquals('99943326', $tfa->getCode($secret, 1111111111));
|
||||
$this->assertEquals('93441116', $tfa->getCode($secret, 1234567890));
|
||||
$this->assertEquals('38618901', $tfa->getCode($secret, 2000000000));
|
||||
$this->assertEquals('47863826', $tfa->getCode($secret, 20000000000));
|
||||
$this->assertSame('90693936', $tfa->getCode($secret, 59));
|
||||
$this->assertSame('25091201', $tfa->getCode($secret, 1111111109));
|
||||
$this->assertSame('99943326', $tfa->getCode($secret, 1111111111));
|
||||
$this->assertSame('93441116', $tfa->getCode($secret, 1234567890));
|
||||
$this->assertSame('38618901', $tfa->getCode($secret, 2000000000));
|
||||
$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);
|
||||
|
||||
$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
|
||||
|
||||
@@ -19,8 +19,8 @@ class EndroidQRCodeTest extends TestCase
|
||||
$qr = new EndroidQrCodeProvider();
|
||||
$tfa = new TwoFactorAuth('Test&Issuer', 6, 30, Algorithm::Sha1, $qr);
|
||||
$data = $this->DecodeDataUri($tfa->getQRCodeImageAsDataUri('Test&Label', 'VMR466AB62ZBOKHE'));
|
||||
$this->assertEquals('image/png', $data['mimetype']);
|
||||
$this->assertEquals('base64', $data['encoding']);
|
||||
$this->assertSame('image/png', $data['mimetype']);
|
||||
$this->assertSame('base64', $data['encoding']);
|
||||
$this->assertNotEmpty($data['data']);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user