Compare commits

..

53 Commits

Author SHA1 Message Date
Will Power ab93dd41ce Merge pull request #115 from Mattie112/endroidqr-v5
Fix issue #114 (Support for EndroidQR v5)
2023-11-14 12:50:27 +00:00
William Hall ec35073c06 🔧 test all endroid versions we can care about 2023-11-14 12:49:15 +00:00
Matthijs van Schooten 13a56018f4 Fixed typo 2023-11-10 13:43:28 +01:00
Matthijs van Schooten 4b6b0601b7 Fixed check ordering 2023-11-10 13:41:25 +01:00
Matthijs van Schooten 126afe4c25 Removed dependency again from composer 2023-11-10 13:32:44 +01:00
Matthijs van Schooten 63f6259db2 Added support for EndroidQR v5 2023-11-10 13:27:45 +01:00
William Hall 137df4dd3c 🔧 remove deprecated rule https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/pull/7066 2023-07-29 15:46:20 +01:00
Will Power 9476751d6b Merge pull request #110 from modelrailroader/patch-1
Fix docs: Construct new TwoFactorAuth-class
2023-07-29 15:36:31 +01:00
Jan 0565d63e6a fix: better understanding for QrCodeProvider class 2023-06-23 21:21:37 +02:00
Jan d5aa22a96f fix: hyperlinks for new file extensions 2023-06-23 17:56:25 +02:00
Jan a6f7735b9f fix: docs for new PHP Version 2023-06-23 17:54:58 +02:00
Jan 6416e79c3e fix: fixed typing 2023-06-11 12:22:26 +02:00
Jan 83611592db fix: fixed documentation 2023-06-11 12:20:59 +02:00
Jan dd0ecaeed1 Fix: Changes for > PHP 8 2023-06-08 16:56:08 +02:00
Jan 241dfec585 fix: fixed docs 2023-06-05 16:09:42 +02:00
Rob Janssen 63b49ce25c Merge pull request #108 from xHeaven/master
code improvements, readability enhancements, type safety, modern syntax adaptation
2023-06-04 01:03:17 +02:00
Mark Magyar e554a9b5e4 change all $qrtext to camelCase version 2023-05-28 19:47:10 +02:00
Mark Magyar 9bc454f425 use null coalescing assignment instead of just null coalescing 2023-05-28 18:58:39 +02:00
Mark Magyar c2183e16b3 use property promotion for options 2023-05-28 18:46:07 +02:00
Mark Magyar cf84371dad use http_build_query instead of sprintf to improve readability 2023-05-28 01:58:44 +02:00
Mark Magyar 4d91d6ae27 use sprintf instead of concatenating 2023-05-28 01:13:12 +02:00
Mark Magyar d60113a77a drop setAccessible calls as they its NOOP since PHP 8.1 2023-05-27 22:02:24 +02:00
Mark Magyar 3f3327e2a1 use type safe assertions 2023-05-27 22:01:34 +02:00
Mark Magyar 8bf962ecee add readonly identifier 2023-05-27 22:00:48 +02:00
Mark Magyar b80431341e remove pointless +0 2023-05-27 22:00:11 +02:00
Mark Magyar f542788673 fix formatting 2023-05-27 21:59:55 +02:00
Mark Magyar ab51d16de7 remove redundant assignment, use shorthard for nullable type 2023-05-27 21:43:04 +02:00
Mark Magyar d4f6aa1ee6 add missing @throws tag 2023-05-27 21:41:10 +02:00
Mark Magyar 04ff3e7e3f fix nullable return type hint 2023-05-27 21:37:44 +02:00
Mark Magyar d94ee9c769 remove useless @throws because it never throws 2023-05-27 21:36:04 +02:00
Mark Magyar 955ce522f0 add readonly modifiers wherever possible 2023-05-27 21:33:54 +02:00
Mark Magyar e584f0c56a remove unnecessary type casts 2023-05-27 21:30:29 +02:00
Mark Magyar 5949a29b89 use null coalescing operator instead of if checks 2023-05-27 21:29:57 +02:00
Mark Magyar 8bf8800523 use empty string comparison instead of strlen call 2023-05-27 21:29:57 +02:00
Mark Magyar 2fd60fad5e use type safe asserts 2023-05-27 21:29:57 +02:00
Mark Magyar 26413e69f0 extract strlen check from loop 2023-05-27 21:29:57 +02:00
Mark Magyar 3adb3e54e5 add missing delimiter for proper escaping 2023-05-27 21:29:57 +02:00
Mark Magyar c45f7a23c9 fix return types for v3, replace switch with match 2023-05-27 21:29:57 +02:00
Mark Magyar bdfa67d1f6 use static keyword for anonymous function definition 2023-05-27 21:29:57 +02:00
Mark Magyar 7675282f45 put scalar types to the right 2023-05-27 21:29:57 +02:00
Mark Magyar 1ca305ec4a use spread syntax instead of array_merge 2023-05-27 21:29:57 +02:00
Mark Magyar aadc32e85e make parameter explicitly nullable 2023-05-27 21:29:57 +02:00
Mark Magyar 9a1aeb8c1f fix formatting 2023-05-27 20:19:48 +02:00
Mark Magyar 4c8a88224a replace switch with match 2023-05-27 20:14:33 +02:00
Mark Magyar 9523b35d34 use strict comparison 2023-05-27 20:06:37 +02:00
Mark Magyar cc9400d901 fix possible typo in parameter name
Interface's method signature:
```php
public function getQRCodeImage(string $qrtext, int $size): string;
```
2023-05-27 20:05:58 +02:00
Mark Magyar dfb5c1a113 make $borderWidth readonly 2023-05-27 20:04:08 +02:00
William Hall 098dce6735 📚 duplicate default provider warning into docs 2023-04-30 14:36:00 +01:00
Rob Janssen ae1cc5e8af Merge pull request #105 from RMEngels/master
Add warning of default QR Code provider to README.md
2023-04-30 01:27:44 +02:00
RMEngels 6e71f43bb1 Add warning of default QR Code provider to README.md 2023-04-25 11:30:44 +02:00
Will Power 5968be2cee Merge pull request #102 from NicolasCARPi/badges
Fix badges in README
2023-02-27 10:01:43 +00:00
Nicolas CARPi 04e26c9961 remove codeclimate badge because coverage is not uploaded there anyway 2023-02-25 13:07:20 +01:00
Nicolas CARPi 8391c5efb0 fix build badge and codeclimate badge
see: https://github.com/badges/shields/issues/8671
2023-02-25 13:05:01 +01:00
29 changed files with 243 additions and 228 deletions
+2 -2
View File
@@ -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
-1
View File
@@ -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,
+8 -1
View File
@@ -1,6 +1,6 @@
# ![Logo](https://raw.githubusercontent.com/RobThree/TwoFactorAuth/master/logo.png) PHP library for Two Factor Authentication
[![Build status](https://img.shields.io/github/workflow/status/RobThree/TwoFactorAuth/Test/master?style=flat-square)](https://github.com/RobThree/TwoFactorAuth/actions?query=branch%3Amaster) [![Latest Stable Version](https://img.shields.io/packagist/v/robthree/twofactorauth.svg?style=flat-square)](https://packagist.org/packages/robthree/twofactorauth) [![License](https://img.shields.io/packagist/l/robthree/twofactorauth.svg?style=flat-square)](LICENSE) [![Downloads](https://img.shields.io/packagist/dt/robthree/twofactorauth.svg?style=flat-square)](https://packagist.org/packages/robthree/twofactorauth) [![Code Climate](https://img.shields.io/codeclimate/github/RobThree/TwoFactorAuth.svg?style=flat-square)](https://codeclimate.com/github/RobThree/TwoFactorAuth) [![PayPal donate button](http://img.shields.io/badge/paypal-donate-orange.svg?style=flat-square)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=6MB5M2SQLP636 "Keep me off the streets")
[![Build status](https://img.shields.io/github/actions/workflow/status/robthree/twofactorauth/test.yml?branch=master)](https://github.com/RobThree/TwoFactorAuth/actions?query=branch%3Amaster) [![Latest Stable Version](https://img.shields.io/packagist/v/robthree/twofactorauth.svg?style=flat-square)](https://packagist.org/packages/robthree/twofactorauth) [![License](https://img.shields.io/packagist/l/robthree/twofactorauth.svg?style=flat-square)](LICENSE) [![Downloads](https://img.shields.io/packagist/dt/robthree/twofactorauth.svg?style=flat-square)](https://packagist.org/packages/robthree/twofactorauth) [![PayPal donate button](http://img.shields.io/badge/paypal-donate-orange.svg?style=flat-square)](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
+9 -9
View File
@@ -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
View File
@@ -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
);
```
+11 -17
View File
@@ -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);
});
}
+36 -17
View File
@@ -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);
}
}
+1 -1
View File
@@ -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
{
+2 -2
View File
@@ -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
+11 -7
View File
@@ -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);
}
}
+15 -12
View File
@@ -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
+13 -10
View File
@@ -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);
}
}
+1 -1
View File
@@ -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)) {
+1 -1
View File
@@ -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)
{
}
+4 -10
View File
@@ -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);
+1 -1
View File
@@ -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
View File
@@ -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');
}
+6 -6
View File
@@ -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
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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());
+8 -8
View File
@@ -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());
+1 -1
View File
@@ -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
View File
@@ -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));
}
}
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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']);
}
}