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