Compare commits

...

39 Commits

Author SHA1 Message Date
Will Power ec82d397ee 📚 consistent changelog links to github (#135) 2024-05-27 11:23:05 +01:00
Nicolas CARPi fc3adc7245 mention #130 in changelog 2024-05-17 14:46:27 +02:00
Rob Janssen 337e96b7af Merge pull request #134 from RobThree/nico-coc
add a code of conduct
2024-05-12 14:21:55 +02:00
Nicolas CARPi 91c091c316 add a code of conduct
so we can point to it when needed
2024-05-12 14:06:05 +02:00
Nicolas CARPi 9e8b31ebbc mention #130 in changelog 2024-05-12 13:52:10 +02:00
Nicolas CARPi df43660714 handle curl errors. fix #129
if curl fails for some reason to get a QR code from an external (http) provider, the app will throw a TwoFactorAuthException.

also fix the demo page with new constructor signature
2024-05-07 14:14:34 +02:00
Nicolas CARPi 6194bb08a7 throw a QRException instead
and change the function signature
2024-05-07 03:16:01 +02:00
Nicolas CARPi 6f78141196 handle curl errors. fix #129
if curl fails for some reason to get a QR code from an external (http)
provider, the app will throw a TwoFactorAuthException.

also fix the demo page with new constructor signature
2024-05-07 03:08:31 +02:00
Nicolas CARPi f5eb9a7051 Changelog for 3.x . PR #127 2024-04-28 12:27:24 +02:00
Nicolas CARPi 5c97ce9be8 Update CHANGELOG.md
Co-authored-by: Will Power <1619102+willpower232@users.noreply.github.com>
2024-04-28 00:22:58 +02:00
Nicolas CARPi ab677e43ee Update CHANGELOG.md
propose the use of named arguments

Co-authored-by: Will Power <1619102+willpower232@users.noreply.github.com>
2024-04-28 00:22:41 +02:00
Nicolas CARPi eb02cb7095 fix typos 2024-04-27 19:26:09 +02:00
Nicolas CARPi c15668dc6d Changelog for 3.x 2024-04-27 19:23:24 +02:00
Nicolas CARPi f16ea16e1b set verify ssl parameter for HTTP based QR provider to true by default PR #126 2024-04-27 18:58:46 +02:00
Nicolas CARPi f35f2ae51a make QR Code Provider a mandatory constructor argument PR #125
This change is discussed in #104
Currently, the library defaults to a QR Code Provider using an external service, thus leaking secrets.

This change forces the definition of a QR Code Provider in the constructor. It is a breaking change.

fixes #104

The public function getQRCodeProvider() has been removed. It is provided by the user in the constructor, so it doesn't make a lot of sense to keep a getter around if we're not using it internally.
2024-04-27 18:58:17 +02:00
Nicolas CARPi cd27effc02 use https protocol for qricket 2024-04-26 18:46:14 +02:00
Nicolas CARPi 2ef7b8e2c6 simplify installation instructions based on Will's comment 2024-04-26 18:45:08 +02:00
Nicolas CARPi b964b2abbf set verify ssl parameter for HTTP based QR provider to true by default
it's already bad to use an external provider, let's use the safest
defaults at least
2024-04-25 21:40:49 +02:00
Nicolas CARPi 8007233058 remove mention of default external provider 2024-04-25 21:31:38 +02:00
Nicolas CARPi 22d45f2ab4 fix documentation link 2024-04-25 21:28:48 +02:00
Nicolas CARPi 2080319f6f make QR Code Provider a mandatory constructor argument
This change is discussed in #104
Currently, the library defaults to a QR Code Provider using an external
service, thus leaking secrets.

This change forces the definition of a QR Code Provider in the
constructor. It is a breaking change.

fixes #104
2024-04-25 21:23:39 +02:00
Will Power cabcf5d493 Merge pull request #124 from RobThree/bacon-has-multiple-versions-now
Bacon has multiple versions now
2024-04-19 12:26:01 +01:00
William Hall 8477141a36 🔧 bump to remove deprecation warnings 2024-04-19 11:40:57 +01:00
William Hall 028867dec2 🚨 bacon has multiple versions now 2024-04-19 11:29:56 +01:00
Rob Janssen dfc1124f96 Merge pull request #117 from Mattie112/patch-1
Changed default secret length from 80bits to 160bits as recommended by RFC4226
2024-04-17 20:46:47 +02:00
Rob Janssen d4a5026d86 Revert back to the intended 160 bits 2024-04-17 20:43:49 +02:00
Rob Janssen bb6802c093 Merge branch 'master' into patch-1 2024-04-17 20:36:16 +02:00
Rob Janssen b909cb38f6 Merge pull request #119 from NicolasCARPi/nico-sensitive-attr
Add SensitiveParameter to sensitive arguments
2024-04-17 20:28:42 +02:00
Nicolas CARPi e08b0a5445 Merge branch 'master' into nico-sensitive-attr
* master:
  add CI4-auth link in README. fix #107 (#123)
  remove insecure rng providers and remove polyfill for hash_equals (#122)
  delete files specific to code editors (#120)
  Exclude useless files from dist archive #103
2024-04-16 18:21:01 +02:00
Nicolas CARPi ecef270ba7 add CI4-auth link in README. fix #107 (#123) 2024-04-16 17:16:48 +01:00
Nicolas CARPi 194ecc28eb remove insecure rng providers and remove polyfill for hash_equals (#122)
* remove insecure rng providers

and remove the openssl provider. We now rely exclusively on
random_bytes(), as there are no reasons not to. Fix #121

* remove the isSecure property of the test rng class

* remove pointless test rng class

we were testing a test class, which didn't make a lot of sense.

* Revert "remove pointless test rng class"

This reverts commit f6da6bee6d.

* Reapply "remove pointless test rng class"

This reverts commit 06220d4a54.

* assing rng provider to class attribute

this also aligns with other providers

* remove polyfill for hash_equals
2024-04-16 16:52:51 +01:00
Nicolas CARPi 323053bd52 delete files specific to code editors (#120)
* delete files specific to code editors

* remove TwoFactorAuth.phpproj
* remove TwoFactorAuth.sln

They are not used anymore. See https://github.com/RobThree/TwoFactorAuth/pull/119#issuecomment-2057777036

* remove deleted files from excluded files in composer.json
2024-04-16 14:44:25 +01:00
Nicolas CARPi 83c7449270 use a link in CHANGELOG.md for SensitiveParameter 2024-04-15 22:43:29 +02:00
Your Name 086a3758ec Exclude useless files from dist archive #103
Co-authored-by: Cédric Anne <cedric.anne@gmail.com>
2024-04-15 21:23:58 +01:00
Nicolas CARPi bba4c207c2 use global import 2024-04-15 21:43:53 +02:00
Nicolas CARPi 061a2a39b4 don't include the sensitive param change in breaking changes section
in changelog
2024-04-15 21:42:23 +02:00
Nicolas CARPi 76f420e6b2 add SensitiveParameter to sensitive arguments
This change adds the PHP attribute SensitiveParameter to the secret
holding variables.
See: https://www.php.net/manual/en/class.sensitiveparameter

This feature is only available in PHP 8.2, so the minimum php version
required has been updated.

Github Actions now use PHP 8.2 and 8.3 for the tests.
The checkout action has been updated to v4, too.

Fix issue #118
2024-04-15 21:40:19 +02:00
Matthijs 319a58a001 Update IRNGProviderTest.php 2024-03-04 16:16:31 +01:00
Matthijs d996779182 Changed default secret bits from 80 to 160 as recommended by RFC4226
https://www.ietf.org/rfc/rfc4226.txt

(and TOTP refers to RFC4226, see https://www.ietf.org/rfc/rfc6238.txt)
2023-11-17 13:53:13 +01:00
37 changed files with 193 additions and 487 deletions
+12
View File
@@ -0,0 +1,12 @@
/.github/ export-ignore
/demo/ export-ignore
/docs/ export-ignore
/tests/ export-ignore
/testsDependency/ export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/.php-cs-fixer.dist.php export-ignore
/logo.png export-ignore
/multifactorauthforeveryone.png export-ignore
/phpstan.neon export-ignore
/phpunit.xml export-ignore
+1
View File
@@ -0,0 +1 @@
Don't be a jerk!
+5 -4
View File
@@ -10,10 +10,11 @@ jobs:
strategy:
matrix:
php-version: ['8.1', '8.2']
php-version: ['8.2', '8.3']
bacon-version: ['^2', '^3']
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
@@ -22,9 +23,9 @@ jobs:
coverage: xdebug
ini-values: error_reporting=E_ALL
- uses: ramsey/composer-install@v2
- uses: ramsey/composer-install@v3
- run: composer require bacon/bacon-qr-code
- run: composer require bacon/bacon-qr-code:${{ matrix.bacon-version }}
- run: composer lint-ci
- run: composer test testsDependency/BaconQRCodeTest.php
+3 -3
View File
@@ -10,11 +10,11 @@ jobs:
strategy:
matrix:
php-version: ['8.1', '8.2']
php-version: ['8.2', '8.3']
endroid-version: ["^3","^4","^5"]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
@@ -23,7 +23,7 @@ jobs:
coverage: xdebug
ini-values: error_reporting=E_ALL
- uses: ramsey/composer-install@v2
- uses: ramsey/composer-install@v3
- run: composer require endroid/qrcode:${{ matrix.endroid-version }} -W
+3 -3
View File
@@ -10,10 +10,10 @@ jobs:
strategy:
matrix:
php-version: ['8.1', '8.2']
php-version: ['8.2', '8.3']
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
@@ -22,7 +22,7 @@ jobs:
coverage: xdebug
ini-values: error_reporting=E_ALL
- uses: ramsey/composer-install@v2
- uses: ramsey/composer-install@v3
- run: composer lint-ci
- run: composer phpstan
+39
View File
@@ -1,5 +1,44 @@
# RobThree\TwoFactorAuth changelog
# Version 3.x
## Breaking changes
### PHP Version
Version 3.x requires at least PHP 8.2.
### Constructor signature change
In order to ensure users of this library make a conscious choice of QR Code Provider, the QR Code Provider is now a mandatory argument, in first place.
If you didn't provide one explicitly before, you can get the old behavior with:
~~~php
use RobThree\Auth\TwoFactorAuth;
use RobThree\Auth\Providers\Qr\QRServerProvider;
$tfa = new TwoFactorAuth(new QRServerProvider());
~~~
If you provided one before, the order of the parameters have been changed, so simply move the QRCodeProvider argument to the first place or use named arguments.
Documentation on selecting a QR Code Provider is available here: [QR Code Provider documentation](https://robthree.github.io/TwoFactorAuth/qr-codes.html).
### Default secret length
The default secret length has been increased from 80 bits to 160 bits (RFC4226) PR [#117](https://github.com/RobThree/TwoFactorAuth/pull/117). This might cause an issue in your application if you were previously storing secrets in a column with restricted size. This change doesn't impact existing secrets, only new ones will get longer.
Previously a secret was 16 characters, now it needs to be stored in a 32 characters width column.
You can keep the old behavior by setting `80` as argument to `createSecret()` (not recommended, see [#117](https://github.com/RobThree/TwoFactorAuth/pull/117) for further discussion).
## Other changes
* The new PHP attribute [SensitiveParameter](https://www.php.net/manual/en/class.sensitiveparameter.php) was added to the code, to prevent accidental leak of secrets in stack traces.
* Likely not breaking anything, but now all external QR Code providers use HTTPS with a verified certificate. PR [#126](https://github.com/RobThree/TwoFactorAuth/pull/126).
* The CSPRNG is now exclusively using `random_bytes()` PHP function. Previously a fallback to `openssl` or non cryptographically secure PRNG existed, they have been removed. PR [#122](https://github.com/RobThree/TwoFactorAuth/pull/122).
* If an external QR code provider is used and the HTTP request results in an error, it will throw a `QRException`. Previously the error was ignored. PR [#130](https://github.com/RobThree/TwoFactorAuth/pull/130), fixes [#129](https://github.com/RobThree/TwoFactorAuth/issues/129).
# Version 2.x
## Breaking changes
+2 -2
View File
@@ -17,9 +17,8 @@ You can make use of the included [Endroid](https://robthree.github.io/TwoFactorA
## Requirements
* Requires PHP version >=8.1
* Requires PHP version >=8.2
* [cURL](http://php.net/manual/en/book.curl.php) when using the provided `QRServerProvider` (default), `ImageChartsQRCodeProvider` or `QRicketProvider` but you can also provide your own QR-code provider.
* [random_bytes()](http://php.net/manual/en/function.random-bytes.php), [OpenSSL](http://php.net/manual/en/book.openssl.php) or [Hash](http://php.net/manual/en/book.hash.php) depending on which built-in RNG you use (TwoFactorAuth will try to 'autodetect' and use the best available); however: feel free to provide your own (CS)RNG.
Optionally, you may need:
@@ -42,6 +41,7 @@ If you need more in-depth information about the configuration available then you
## Integrations
- [CakePHP 3](https://github.com/andrej-griniuk/cakephp-two-factor-auth)
- [CI4-Auth: a user, group, role and permission management library for Codeigniter 4](https://github.com/glewe/ci4-auth)
## License
-69
View File
@@ -1,69 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Name>TwoFactorAuth</Name>
<ProjectGuid>{e569f53a-a604-4579-91ce-4e35b27da47b}</ProjectGuid>
<RootNamespace>TwoFactorAuth</RootNamespace>
<OutputType>Library</OutputType>
<ProjectTypeGuids>{A0786B88-2ADB-4C21-ABE8-AA2D79766269}</ProjectTypeGuids>
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
<Server>PHPDev</Server>
<PublishEvent>None</PublishEvent>
<PHPDevAutoPort>True</PHPDevAutoPort>
<PHPDevPort>41315</PHPDevPort>
<PHPDevHostName>localhost</PHPDevHostName>
<IISProjectUrl>http://localhost:41315/</IISProjectUrl>
<Runtime>PHP</Runtime>
<RuntimeVersion>8.1</RuntimeVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<IncludeDebugInformation>true</IncludeDebugInformation>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<IncludeDebugInformation>false</IncludeDebugInformation>
</PropertyGroup>
<ItemGroup>
<Compile Include="demo\demo.php" />
<Compile Include="demo\loader.php" />
<Compile Include="lib\Providers\Qr\BaseHTTPQRCodeProvider.php" />
<Compile Include="lib\Providers\Qr\ImageChartsQRCodeProvider.php" />
<Compile Include="lib\Providers\Qr\IQRCodeProvider.php" />
<Compile Include="lib\Providers\Qr\QRException.php" />
<Compile Include="lib\Providers\Qr\QRicketProvider.php" />
<Compile Include="lib\Providers\Qr\QRServerProvider.php" />
<Compile Include="lib\Providers\Rng\CSRNGProvider.php" />
<Compile Include="lib\Providers\Rng\IRNGProvider.php" />
<Compile Include="lib\Providers\Rng\OpenSSLRNGProvider.php" />
<Compile Include="lib\Providers\Rng\HashRNGProvider.php" />
<Compile Include="lib\Providers\Rng\RNGException.php" />
<Compile Include="lib\Providers\Time\HttpTimeProvider.php" />
<Compile Include="lib\Providers\Time\ITimeProvider.php" />
<Compile Include="lib\Providers\Time\LocalMachineTimeProvider.php" />
<Compile Include="lib\Providers\Time\NTPTimeProvider.php" />
<Compile Include="lib\Providers\Time\TimeException.php" />
<Compile Include="lib\TwoFactorAuth.php" />
<Compile Include=".gitignore" />
<Compile Include="README.md" />
<Compile Include="lib\TwoFactorAuthException.php" />
<Compile Include="tests\TwoFactorAuthTest.php" />
</ItemGroup>
<ItemGroup>
<Folder Include="lib\" />
<Folder Include="lib\Providers\" />
<Folder Include="lib\Providers\Time\" />
<Folder Include="lib\Providers\Qr\" />
<Folder Include="lib\Providers\Rng\" />
<Folder Include="demo\" />
<Folder Include="tests\" />
</ItemGroup>
<ItemGroup>
<Content Include=".travis.yml" />
<Content Include="composer.json" />
<Content Include="composer.lock" />
<Content Include="logo.png" />
<Content Include="multifactorauthforeveryone.png" />
<Content Include="LICENSE" />
<Content Include="phpunit.xml" />
</ItemGroup>
</Project>
-22
View File
@@ -1,22 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
VisualStudioVersion = 12.0.30723.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{A0786B88-2ADB-4C21-ABE8-AA2D79766269}") = "TwoFactorAuth", "TwoFactorAuth.phpproj", "{E569F53A-A604-4579-91CE-4E35B27DA47B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E569F53A-A604-4579-91CE-4E35B27DA47B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E569F53A-A604-4579-91CE-4E35B27DA47B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E569F53A-A604-4579-91CE-4E35B27DA47B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E569F53A-A604-4579-91CE-4E35B27DA47B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
+1 -16
View File
@@ -27,7 +27,7 @@
"source": "https://github.com/RobThree/TwoFactorAuth"
},
"require": {
"php": ">=8.1.0"
"php": ">=8.2.0"
},
"require-dev": {
"phpunit/phpunit": "^9",
@@ -61,20 +61,5 @@
"test": [
"XDEBUG_MODE=coverage phpunit"
]
},
"archive": {
"exclude": [
"/.github/",
"/demo/",
"/docs/",
"/tests/",
"/testsDependency/",
"/.gitignore",
"/logo.png",
"/multifactorauthforeveryone.png",
"/phpunit.xml",
"/TwoFactorAuth.phpproj",
"/TwoFactorAuth.sln"
]
}
}
+1 -1
View File
@@ -12,7 +12,7 @@
});
// substitute your company or app name here
$tfa = new RobThree\Auth\TwoFactorAuth('RobThree TwoFactorAuth');
$tfa = new RobThree\Auth\TwoFactorAuth(new RobThree\Auth\Providers\Qr\QRServerProvider());
?>
<li>First create a secret and associate it with a user</li>
<?php
+15 -10
View File
@@ -7,28 +7,33 @@ title: Getting Started
The best way of making use of this project is by installing it with [composer](https://getcomposer.org/doc/01-basic-usage.md).
```
php composer.phar require robthree/twofactorauth
```
or if you have composer installed globally
```
composer require robthree/twofactorauth
```
## 2. Create an instance
Now you can create an instance for use with your code
`TwoFactorAuth` constructor requires an object able to provide a QR Code image. It is the only mandatory argument. This lets you select your preferred QR Code generator/library.
See [QR code providers documentation](qr-codes.md) for more information about the different possibilites.
Example code:
```php
use RobThree\Auth\TwoFactorAuth;
use RobThree\Auth\Providers\Qr\BaconQrCodeProvider; // if using Bacon
use RobThree\Auth\Providers\Qr\EndroidQrCodeProvider; // if using Endroid
$tfa = new TwoFactorAuth();
// using Bacon
$tfa = new TwoFactorAuth(new BaconQrCodeProvider());
// using Endroid
$tfa = new TwoFactorAuth(new EndroidQrCodeProvider());
// using a custom object implementing IQRCodeProvider interface
$tfa = new TwoFactorAuth(new MyQrCodeProvider());
// using named argument and a variable
$tfa = new TwoFactorAuth(qrcodeprovider: $qrGenerator);
```
**Note:** if you are not using a framework that uses composer, you should [include the composer loader yourself](https://getcomposer.org/doc/01-basic-usage.md#autoloading)
## 3. Shared secrets
When your user is setting up two-factor, or multi-factor, authentication in your project, you can create a secret from the instance.
+1 -9
View File
@@ -21,15 +21,7 @@ Argument | Default value | Use
### RNG providers
This library also comes with some [Random Number Generator (RNG)](https://en.wikipedia.org/wiki/Random_number_generation) providers. The RNG provider generates a number of random bytes and returns these bytes as a string. These values are then used to create the secret. By default (no RNG provider specified) TwoFactorAuth will try to determine the best available RNG provider to use in this order.
1. [CSRNGProvider](https://github.com/RobThree/TwoFactorAuth/blob/master/lib/Providers/Rng/CSRNGProvider.php) for PHP7+
2. [OpenSSLRNGProvider](https://github.com/RobThree/TwoFactorAuth/blob/master/lib/Providers/Rng/OpenSSLRNGProvider.php) where openssl is available
3. [HashRNGProvider](https://github.com/RobThree/TwoFactorAuth/blob/master/lib/Providers/Rng/HashRNGProvider.php) **non-cryptographically secure** fallback
Each of these RNG providers have some constructor arguments that allow you to tweak some of the settings to use when creating the random bytes.
You can also implement your own by implementing the [`IRNGProvider` interface](https://github.com/RobThree/TwoFactorAuth/blob/master/lib/Providers/Rng/IRNGProvider.php).
Should you feel the need to use a CSPRNG different than `random_bytes()`, you can use the `rngprovider` argument of the constructor to provide an object implementing the [`IRNGProvider`](https://github.com/RobThree/TwoFactorAuth/blob/master/lib/Providers/Rng/IRNGProvider.php) interface.
### Time providers
+23 -25
View File
@@ -5,7 +5,7 @@ title: QR Codes
An alternative way of communicating the secret to the user is through the use of [QR Codes](http://en.wikipedia.org/wiki/QR_code) which most if not all authenticator mobile apps can scan.
This can avoid accidental typing errors and also pre-set some text values within the users app.
This can avoid accidental typing errors and also pre-set some text values within the two factor authentication mobile application.
You can display the QR Code as a base64 encoded image using the instance as follows, supplying the users name or other public identifier as the first argument
@@ -16,18 +16,6 @@ You can display the QR Code as a base64 encoded image using the instance as foll
You can also specify a size as a third argument which is 200 by default.
**Note:** by default, the QR code returned by the instance is generated from a third party across the internet. If the third party is encountering problems or is not available from where you have hosted your code, your user will likely experience a delay in seeing the QR code, if it even loads at all. This can be overcome with offline providers configured when you create the instance.
## Online Providers
[QRServerProvider](qr-codes/qr-server.md) (default)
**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.
[ImageChartsQRCodeProvider](qr-codes/image-charts.md)
[QRicketProvider](qr-codes/qrickit.md)
## Offline Providers
[EndroidQrCodeProvider](qr-codes/endroid.md) and EndroidQrCodeWithLogoProvider
@@ -38,23 +26,33 @@ You can also specify a size as a third argument which is 200 by default.
## Custom Provider
If you wish to make your own QR Code provider to reference another service or library, it must implement the [IQRCodeProvider interface](https://github.com/RobThree/TwoFactorAuth/blob/master/lib/Providers/Qr/IQRCodeProvider.php).
If you wish to make your own QR Code provider to reference another service or library, it must implement the [IQRCodeProvider interface](../lib/Providers/Qr/IQRCodeProvider.php).
It is recommended to use similar constructor arguments as the included providers to avoid big shifts when trying different providers.
## Using a specific provider
If you do not want to use the default QR code provider, you can specify the one you want to use when you create your instance.
Example:
```php
use RobThree\Auth\TwoFactorAuth;
$qrCodeProvider = new YourChosenProvider();
$tfa = new TwoFactorAuth(
issuer: "Your Company Or App Name",
qrcodeprovider: $qrCodeProvider
);
// using a custom object implementing IQRCodeProvider
$tfa = new TwoFactorAuth(new MyQrCodeProvider());
// using named argument and a variable
$tfa = new TwoFactorAuth(qrcodeprovider: $qrGenerator);
```
As you create a new instance of your provider, you can supply any extra configuration there.
## Online Providers
**Warning:** Using an external service for generating QR codes encoding authentication secrets is **not** recommended! You should instead make use of the included offline providers listed above.
* Gogr.me: [QRServerProvider](qr-codes/qr-server.md)
* Image Charts: [ImageChartsQRCodeProvider](qr-codes/image-charts.md)
* Qrickit: [QRicketProvider](qr-codes/qrickit.md)
* Google Charts: [GoogleChartsQrCodeProvider](qr-codes/google-charts.md)
Example:
```php
use RobThree\Auth\TwoFactorAuth;
use RobThree\Auth\Providers\Qr\GoogleChartsQrCodeProvider;
$tfa = new TwoFactorAuth(new GoogleChartsQrCodeProvider());
```
+15
View File
@@ -0,0 +1,15 @@
---
layout: post
title: QR GoogleCharts
---
See: https://developers.google.com/chart/infographics/docs/qr_codes
## Optional Configuration
Argument | Default value
------------------------|---------------
`$verifyssl` | `false`
`$errorcorrectionlevel` | `'L'`
`$margin` | `4`
`$encoding` | `'UTF-8'`
+1 -1
View File
@@ -7,7 +7,7 @@ title: Image-Charts
Argument | Default value
------------------------|---------------
`$verifyssl` | `false`
`$verifyssl` | `true`
`$errorcorrectionlevel` | `'L'`
`$margin` | `4`
+1 -1
View File
@@ -7,7 +7,7 @@ title: QR Server
Argument | Default value
------------------------|---------------
`$verifyssl` | `false`
`$verifyssl` | `true`
`$errorcorrectionlevel` | `'L'`
`$margin` | `4`
`$qzone` | `1`
+5 -2
View File
@@ -6,9 +6,9 @@ namespace RobThree\Auth\Providers\Qr;
abstract class BaseHTTPQRCodeProvider implements IQRCodeProvider
{
protected bool $verifyssl;
protected bool $verifyssl = true;
protected function getContent(string $url): string|bool
protected function getContent(string $url): string
{
$curlhandle = curl_init();
@@ -22,6 +22,9 @@ abstract class BaseHTTPQRCodeProvider implements IQRCodeProvider
CURLOPT_USERAGENT => 'TwoFactorAuth',
));
$data = curl_exec($curlhandle);
if ($data === false) {
throw new QRException(curl_error($curlhandle));
}
curl_close($curlhandle);
return $data;
@@ -7,7 +7,7 @@ namespace RobThree\Auth\Providers\Qr;
// https://developers.google.com/chart/infographics/docs/qr_codes
class GoogleChartsQrCodeProvider extends BaseHTTPQRCodeProvider
{
public function __construct(protected bool $verifyssl = false, public string $errorcorrectionlevel = 'L', public int $margin = 4, public string $encoding = 'UTF-8')
public function __construct(protected bool $verifyssl = true, public string $errorcorrectionlevel = 'L', public int $margin = 4, public string $encoding = 'UTF-8')
{
}
@@ -9,7 +9,7 @@ namespace RobThree\Auth\Providers\Qr;
*/
class ImageChartsQRCodeProvider extends BaseHTTPQRCodeProvider
{
public function __construct(protected bool $verifyssl = false, public string $errorcorrectionlevel = 'L', public int $margin = 1)
public function __construct(protected bool $verifyssl = true, public string $errorcorrectionlevel = 'L', public int $margin = 1)
{
}
+2 -2
View File
@@ -5,11 +5,11 @@ declare(strict_types=1);
namespace RobThree\Auth\Providers\Qr;
/**
* Use http://goqr.me/api/doc/create-qr-code/ to get QR code
* Use https://goqr.me/api/doc/create-qr-code/ to get QR code
*/
class QRServerProvider extends BaseHTTPQRCodeProvider
{
public function __construct(protected bool $verifyssl = false, public string $errorcorrectionlevel = 'L', public int $margin = 4, public int $qzone = 1, public string $bgcolor = 'ffffff', public string $color = '000000', public string $format = 'png')
public function __construct(protected bool $verifyssl = true, public string $errorcorrectionlevel = 'L', public int $margin = 4, public int $qzone = 1, public string $bgcolor = 'ffffff', public string $color = '000000', public string $format = 'png')
{
}
+2 -3
View File
@@ -9,9 +9,8 @@ namespace RobThree\Auth\Providers\Qr;
*/
class QRicketProvider extends BaseHTTPQRCodeProvider
{
public function __construct(public string $errorcorrectionlevel = 'L', public string $bgcolor = 'ffffff', public string $color = '000000', public string $format = 'p')
public function __construct(protected bool $verifyssl = true, public string $errorcorrectionlevel = 'L', public string $bgcolor = 'ffffff', public string $color = '000000', public string $format = 'p')
{
$this->verifyssl = false;
}
public function getMimeType(): string
@@ -43,6 +42,6 @@ class QRicketProvider extends BaseHTTPQRCodeProvider
'd' => $qrText,
);
return 'http://qrickit.com/api/qr?' . http_build_query($queryParameters);
return 'https://qrickit.com/api/qr?' . http_build_query($queryParameters);
}
}
-8
View File
@@ -13,12 +13,4 @@ class CSRNGProvider implements IRNGProvider
{
return random_bytes($bytecount); // PHP7+
}
/**
* {@inheritdoc}
*/
public function isCryptographicallySecure(): bool
{
return true;
}
}
-40
View File
@@ -1,40 +0,0 @@
<?php
declare(strict_types=1);
namespace RobThree\Auth\Providers\Rng;
use function in_array;
class HashRNGProvider implements IRNGProvider
{
public function __construct(private readonly string $algorithm = 'sha256')
{
$algos = array_values(hash_algos());
if (!in_array($this->algorithm, $algos, true)) {
throw new RNGException('Unsupported algorithm specified');
}
}
/**
* {@inheritdoc}
*/
public function getRandomBytes(int $bytecount): string
{
$result = '';
$hash = mt_rand();
for ($i = 0; $i < $bytecount; $i++) {
$hash = hash($this->algorithm, $hash . mt_rand(), true);
$result .= $hash[mt_rand(0, strlen($hash) - 1)];
}
return $result;
}
/**
* {@inheritdoc}
*/
public function isCryptographicallySecure(): bool
{
return false;
}
}
-2
View File
@@ -7,6 +7,4 @@ namespace RobThree\Auth\Providers\Rng;
interface IRNGProvider
{
public function getRandomBytes(int $bytecount): string;
public function isCryptographicallySecure(): bool;
}
-29
View File
@@ -1,29 +0,0 @@
<?php
declare(strict_types=1);
namespace RobThree\Auth\Providers\Rng;
class OpenSSLRNGProvider implements IRNGProvider
{
public function __construct(private readonly bool $requirestrong = true)
{
}
/**
* {@inheritdoc}
*/
public function getRandomBytes(int $bytecount): string
{
// will throw an Exception on failure
return openssl_random_pseudo_bytes($bytecount, $crypto_strong);
}
/**
* {@inheritdoc}
*/
public function isCryptographicallySecure(): bool
{
return $this->requirestrong;
}
}
+12 -55
View File
@@ -4,16 +4,16 @@ declare(strict_types=1);
namespace RobThree\Auth;
use function hash_equals;
use RobThree\Auth\Providers\Qr\IQRCodeProvider;
use RobThree\Auth\Providers\Qr\QRServerProvider;
use RobThree\Auth\Providers\Rng\CSRNGProvider;
use RobThree\Auth\Providers\Rng\HashRNGProvider;
use RobThree\Auth\Providers\Rng\IRNGProvider;
use RobThree\Auth\Providers\Rng\OpenSSLRNGProvider;
use RobThree\Auth\Providers\Time\HttpTimeProvider;
use RobThree\Auth\Providers\Time\ITimeProvider;
use RobThree\Auth\Providers\Time\LocalMachineTimeProvider;
use RobThree\Auth\Providers\Time\NTPTimeProvider;
use SensitiveParameter;
// Based on / inspired by: https://github.com/PHPGangsta/GoogleAuthenticator
// Algorithms, digits, period etc. explained: https://github.com/google/google-authenticator/wiki/Key-Uri-Format
@@ -28,11 +28,11 @@ class TwoFactorAuth
private static array $_base32lookup = array();
public function __construct(
private IQRCodeProvider $qrcodeprovider,
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
) {
@@ -51,14 +51,11 @@ class TwoFactorAuth
/**
* Create a new secret
*/
public function createSecret(int $bits = 80, bool $requirecryptosecure = true): string
public function createSecret(int $bits = 160): string
{
$secret = '';
$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');
}
$rnd = $rngprovider->getRandomBytes($bytes);
for ($i = 0; $i < $bytes; $i++) {
$secret .= self::$_base32[ord($rnd[$i]) & 31]; //Mask out left 3 bits for 0-31 values
@@ -69,7 +66,7 @@ class TwoFactorAuth
/**
* Calculate the code with given secret and point in time
*/
public function getCode(string $secret, ?int $time = null): string
public function getCode(#[SensitiveParameter] string $secret, ?int $time = null): string
{
$secretkey = $this->base32Decode($secret);
@@ -98,7 +95,7 @@ class TwoFactorAuth
for ($i = -$discrepancy; $i <= $discrepancy; $i++) {
$ts = $timestamp + ($i * $this->period);
$slice = $this->getTimeSlice($ts);
$timeslice = $this->codeEquals($this->getCode($secret, $ts), $code) ? $slice : $timeslice;
$timeslice = hash_equals($this->getCode($secret, $ts), $code) ? $slice : $timeslice;
}
return $timeslice > 0;
@@ -107,17 +104,16 @@ class TwoFactorAuth
/**
* Get data-uri of QRCode
*/
public function getQRCodeImageAsDataUri(string $label, string $secret, int $size = 200): string
public function getQRCodeImageAsDataUri(string $label, #[SensitiveParameter] string $secret, int $size = 200): string
{
if ($size <= 0) {
throw new TwoFactorAuthException('Size must be > 0');
}
$qrcodeprovider = $this->getQrCodeProvider();
return 'data:'
. $qrcodeprovider->getMimeType()
. $this->qrcodeprovider->getMimeType()
. ';base64,'
. base64_encode($qrcodeprovider->getQRCodeImage($this->getQRText($label, $secret), $size));
. base64_encode($this->qrcodeprovider->getQRCodeImage($this->getQRText($label, $secret), $size));
}
/**
@@ -153,7 +149,7 @@ class TwoFactorAuth
/**
* Builds a string to be encoded in a QR code
*/
public function getQRText(string $label, string $secret): string
public function getQRText(string $label, #[SensitiveParameter] string $secret): string
{
return 'otpauth://totp/' . rawurlencode($label)
. '?secret=' . rawurlencode($secret)
@@ -163,30 +159,12 @@ class TwoFactorAuth
. '&digits=' . $this->digits;
}
public function getQrCodeProvider(): IQRCodeProvider
{
// Set default QR Code provider if none was specified
return $this->qrcodeprovider ??= new QRServerProvider();
}
/**
* @throws TwoFactorAuthException
*/
public function getRngProvider(): IRNGProvider
{
if ($this->rngprovider !== null) {
return $this->rngprovider;
}
if (function_exists('random_bytes')) {
return $this->rngprovider = new CSRNGProvider();
}
if (function_exists('openssl_random_pseudo_bytes')) {
return $this->rngprovider = new OpenSSLRNGProvider();
}
if (function_exists('hash')) {
return $this->rngprovider = new HashRNGProvider();
}
throw new TwoFactorAuthException('Unable to find a suited RNGProvider');
return $this->rngprovider ??= new CSRNGProvider();
}
public function getTimeProvider(): ITimeProvider
@@ -195,27 +173,6 @@ class TwoFactorAuth
return $this->timeprovider ??= new LocalMachineTimeProvider();
}
/**
* Timing-attack safe comparison of 2 codes (see http://blog.ircmaxell.com/2014/11/its-all-about-time.html)
*/
private function codeEquals(string $safe, string $user): bool
{
if (function_exists('hash_equals')) {
return hash_equals($safe, $user);
}
// In general, it's not possible to prevent length leaks. So it's OK to leak the length. The important part is that
// we don't leak information about the difference of the two strings.
if (strlen($safe) === strlen($user)) {
$result = 0;
$strlen = strlen($safe);
for ($i = 0; $i < $strlen; $i++) {
$result |= (ord($safe[$i]) ^ ord($user[$i]));
}
return $result === 0;
}
return false;
}
private function getTime(?int $time = null): int
{
return $time ?? $this->getTimeProvider()->getTime();
+11 -9
View File
@@ -7,6 +7,7 @@ namespace Tests\Providers\Qr;
use PHPUnit\Framework\TestCase;
use RobThree\Auth\Algorithm;
use RobThree\Auth\Providers\Qr\HandlesDataUri;
use RobThree\Auth\Providers\Qr\IQRCodeProvider;
use RobThree\Auth\TwoFactorAuth;
use RobThree\Auth\TwoFactorAuthException;
@@ -14,11 +15,16 @@ class IQRCodeProviderTest extends TestCase
{
use HandlesDataUri;
protected IQRCodeProvider $qr;
protected function setUp(): void
{
$this->qr = new TestQrProvider();
}
public function testTotpUriIsCorrect(): void
{
$qr = new TestQrProvider();
$tfa = new TwoFactorAuth('Test&Issuer', 6, 30, Algorithm::Sha1, $qr);
$tfa = new TwoFactorAuth($this->qr, 'Test&Issuer', 6, 30, Algorithm::Sha1);
$data = $this->DecodeDataUri($tfa->getQRCodeImageAsDataUri('Test&Label', 'VMR466AB62ZBOKHE'));
$this->assertSame('test/test', $data['mimetype']);
$this->assertSame('base64', $data['encoding']);
@@ -27,14 +33,12 @@ class IQRCodeProviderTest extends TestCase
public function testTotpUriIsCorrectNoIssuer(): void
{
$qr = new TestQrProvider();
/**
* The library specifies the issuer is null by default however in PHP 8.1
* there is a deprecation warning for passing null as a string argument to rawurlencode
*/
$tfa = new TwoFactorAuth(null, 6, 30, Algorithm::Sha1, $qr);
$tfa = new TwoFactorAuth($this->qr, null, 6, 30, Algorithm::Sha1);
$data = $this->DecodeDataUri($tfa->getQRCodeImageAsDataUri('Test&Label', 'VMR466AB62ZBOKHE'));
$this->assertSame('test/test', $data['mimetype']);
$this->assertSame('base64', $data['encoding']);
@@ -43,9 +47,7 @@ class IQRCodeProviderTest extends TestCase
public function testGetQRCodeImageAsDataUriThrowsOnInvalidSize(): void
{
$qr = new TestQrProvider();
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, $qr);
$tfa = new TwoFactorAuth($this->qr, 'Test', 6, 30, Algorithm::Sha1);
$this->expectException(TwoFactorAuthException::class);
+3 -11
View File
@@ -11,19 +11,11 @@ class CSRNGProviderTest extends TestCase
{
use NeedsRngLengths;
/**
* @requires function random_bytes
*/
public function testCSRNGProvidersReturnExpectedNumberOfBytes(): void
{
if (function_exists('random_bytes')) {
$rng = new CSRNGProvider();
foreach ($this->rngTestLengths as $l) {
$this->assertSame($l, strlen($rng->getRandomBytes($l)));
}
$this->assertTrue($rng->isCryptographicallySecure());
} else {
$this->expectNotToPerformAssertions();
$rng = new CSRNGProvider();
foreach ($this->rngTestLengths as $l) {
$this->assertSame($l, strlen($rng->getRandomBytes($l)));
}
}
}
@@ -1,26 +0,0 @@
<?php
declare(strict_types=1);
namespace Tests\Providers\Rng;
use PHPUnit\Framework\TestCase;
use RobThree\Auth\Providers\Rng\HashRNGProvider;
class HashRNGProviderTest extends TestCase
{
use NeedsRngLengths;
/**
* @return void
*/
public function testHashRNGProvidersReturnExpectedNumberOfBytes()
{
$rng = new HashRNGProvider();
foreach ($this->rngTestLengths as $l) {
$this->assertSame($l, strlen($rng->getRandomBytes($l)));
}
$this->assertFalse($rng->isCryptographicallySecure());
}
}
+4 -37
View File
@@ -7,46 +7,13 @@ namespace Tests\Providers\Rng;
use PHPUnit\Framework\TestCase;
use RobThree\Auth\Algorithm;
use RobThree\Auth\TwoFactorAuth;
use RobThree\Auth\TwoFactorAuthException;
use Tests\Providers\Qr\TestQrProvider;
class IRNGProviderTest extends TestCase
{
public function testCreateSecretThrowsOnInsecureRNGProvider(): void
public function testCreateSecret(): void
{
$rng = new TestRNGProvider();
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, $rng);
$this->expectException(TwoFactorAuthException::class);
$tfa->createSecret();
}
public function testCreateSecretOverrideSecureDoesNotThrowOnInsecureRNG(): void
{
$rng = new TestRNGProvider();
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, $rng);
$this->assertSame('ABCDEFGHIJKLMNOP', $tfa->createSecret(80, false));
}
public function testCreateSecretDoesNotThrowOnSecureRNGProvider(): void
{
$rng = new TestRNGProvider(true);
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, $rng);
$this->assertSame('ABCDEFGHIJKLMNOP', $tfa->createSecret());
}
public function testCreateSecretGeneratesDesiredAmountOfEntropy(): void
{
$rng = new TestRNGProvider(true);
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, $rng);
$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));
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 6, 30, Algorithm::Sha1, null, null);
$this->assertIsString($tfa->createSecret());
}
}
@@ -1,39 +0,0 @@
<?php
declare(strict_types=1);
namespace Tests\Providers\Rng;
use PHPUnit\Framework\TestCase;
use RobThree\Auth\Providers\Rng\OpenSSLRNGProvider;
class OpenSSLRNGProviderTest extends TestCase
{
use NeedsRngLengths;
/**
* @return void
*/
public function testStrongOpenSSLRNGProvidersReturnExpectedNumberOfBytes()
{
$rng = new OpenSSLRNGProvider(true);
foreach ($this->rngTestLengths as $l) {
$this->assertSame($l, strlen($rng->getRandomBytes($l)));
}
$this->assertTrue($rng->isCryptographicallySecure());
}
/**
* @return void
*/
public function testNonStrongOpenSSLRNGProvidersReturnExpectedNumberOfBytes()
{
$rng = new OpenSSLRNGProvider(false);
foreach ($this->rngTestLengths as $l) {
$this->assertSame($l, strlen($rng->getRandomBytes($l)));
}
$this->assertFalse($rng->isCryptographicallySecure());
}
}
-36
View File
@@ -1,36 +0,0 @@
<?php
declare(strict_types=1);
namespace Tests\Providers\Rng;
use RobThree\Auth\Providers\Rng\IRNGProvider;
class TestRNGProvider implements IRNGProvider
{
public function __construct(private readonly bool $isSecure = false)
{
}
/**
* {@inheritdoc}
*/
public function getRandomBytes(int $bytecount): string
{
$result = '';
for ($i = 0; $i < $bytecount; $i++) {
$result .= chr($i);
}
return $result;
}
/**
* {@inheritdoc}
*/
public function isCryptographicallySecure(): bool
{
return $this->isSecure;
}
}
+4 -3
View File
@@ -8,6 +8,7 @@ use PHPUnit\Framework\TestCase;
use RobThree\Auth\Algorithm;
use RobThree\Auth\TwoFactorAuth;
use RobThree\Auth\TwoFactorAuthException;
use Tests\Providers\Qr\TestQrProvider;
class ITimeProviderTest extends TestCase
{
@@ -17,7 +18,7 @@ class ITimeProviderTest extends TestCase
$tpr1 = new TestTimeProvider(123);
$tpr2 = new TestTimeProvider(128);
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, null, $tpr1);
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 6, 30, Algorithm::Sha1, null, $tpr1);
$tfa->ensureCorrectTime(array($tpr2)); // 128 - 123 = 5 => within default leniency
}
@@ -26,7 +27,7 @@ class ITimeProviderTest extends TestCase
$tpr1 = new TestTimeProvider(123);
$tpr2 = new TestTimeProvider(124);
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, null, $tpr1);
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 6, 30, Algorithm::Sha1, null, $tpr1);
$this->expectException(TwoFactorAuthException::class);
@@ -36,7 +37,7 @@ class ITimeProviderTest extends TestCase
public function testEnsureDefaultTimeProviderReturnsCorrectTime(): void
{
$this->expectNotToPerformAssertions();
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1);
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 6, 30, Algorithm::Sha1);
$tfa->ensureCorrectTime(array(new TestTimeProvider(time())), 1); // Use a leniency of 1, should the time change between both time() calls
}
}
+14 -13
View File
@@ -11,6 +11,7 @@ use RobThree\Auth\Providers\Time\HttpTimeProvider;
use RobThree\Auth\Providers\Time\NTPTimeProvider;
use RobThree\Auth\TwoFactorAuth;
use RobThree\Auth\TwoFactorAuthException;
use Tests\Providers\Qr\TestQrProvider;
class TwoFactorAuthTest extends TestCase
{
@@ -18,26 +19,26 @@ class TwoFactorAuthTest extends TestCase
{
$this->expectException(TwoFactorAuthException::class);
new TwoFactorAuth('Test', 0);
new TwoFactorAuth(new TestQrProvider(), 'Test', 0);
}
public function testConstructorThrowsOnInvalidPeriod(): void
{
$this->expectException(TwoFactorAuthException::class);
new TwoFactorAuth('Test', 6, 0);
new TwoFactorAuth(new TestQrProvider(), 'Test', 6, 0);
}
public function testGetCodeReturnsCorrectResults(): void
{
$tfa = new TwoFactorAuth('Test');
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test');
$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 = new TwoFactorAuth(new TestQrProvider(), 'Test', 6, 30, Algorithm::Sha1);
$tfa->ensureCorrectTime(array(
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??
@@ -50,7 +51,7 @@ class TwoFactorAuthTest extends TestCase
public function testVerifyCodeWorksCorrectly(): void
{
$tfa = new TwoFactorAuth('Test', 6, 30);
$tfa = new TwoFactorAuth(new TestQrProvider(), '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
@@ -69,7 +70,7 @@ class TwoFactorAuthTest extends TestCase
public function testVerifyCorrectTimeSliceIsReturned(): void
{
$tfa = new TwoFactorAuth('Test', 6, 30);
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 6, 30);
// 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
@@ -95,7 +96,7 @@ class TwoFactorAuthTest extends TestCase
public function testGetCodeThrowsOnInvalidBase32String1(): void
{
$tfa = new TwoFactorAuth('Test');
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test');
$this->expectException(TwoFactorAuthException::class);
@@ -104,7 +105,7 @@ class TwoFactorAuthTest extends TestCase
public function testGetCodeThrowsOnInvalidBase32String2(): void
{
$tfa = new TwoFactorAuth('Test');
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test');
$this->expectException(TwoFactorAuthException::class);
@@ -124,7 +125,7 @@ class TwoFactorAuthTest extends TestCase
// "In general, you don't want to break any encapsulation for the sake of testing (or as Mom used to say, "don't
// expose your privates!"). Most of the time, you should be able to test a class by exercising its public methods."
// Dave Thomas and Andy Hunt -- "Pragmatic Unit Testing
$tfa = new TwoFactorAuth('Test');
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test');
$method = new ReflectionMethod(TwoFactorAuth::class, 'base32Decode');
@@ -144,7 +145,7 @@ class TwoFactorAuthTest extends TestCase
// This test ensures that strings without the padding-char ('=') are also decoded correctly.
// https://tools.ietf.org/html/rfc4648#page-4:
// "In some circumstances, the use of padding ("=") in base-encoded data is not required or used."
$tfa = new TwoFactorAuth('Test');
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test');
$method = new ReflectionMethod(TwoFactorAuth::class, 'base32Decode');
@@ -162,7 +163,7 @@ 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);
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 8, 30, Algorithm::Sha1);
$this->assertSame('94287082', $tfa->getCode($secret, 59));
$this->assertSame('07081804', $tfa->getCode($secret, 1111111109));
$this->assertSame('14050471', $tfa->getCode($secret, 1111111111));
@@ -175,7 +176,7 @@ 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);
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 8, 30, Algorithm::Sha256);
$this->assertSame('46119246', $tfa->getCode($secret, 59));
$this->assertSame('68084774', $tfa->getCode($secret, 1111111109));
$this->assertSame('67062674', $tfa->getCode($secret, 1111111111));
@@ -188,7 +189,7 @@ 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);
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 8, 30, Algorithm::Sha512);
$this->assertSame('90693936', $tfa->getCode($secret, 59));
$this->assertSame('25091201', $tfa->getCode($secret, 1111111109));
$this->assertSame('99943326', $tfa->getCode($secret, 1111111111));
+10 -3
View File
@@ -8,6 +8,7 @@ use PHPUnit\Framework\TestCase;
use RobThree\Auth\Algorithm;
use RobThree\Auth\Providers\Qr\BaconQrCodeProvider;
use RobThree\Auth\Providers\Qr\HandlesDataUri;
use RobThree\Auth\Providers\Qr\IQRCodeProvider;
use RobThree\Auth\TwoFactorAuth;
use RuntimeException;
@@ -15,11 +16,17 @@ class BaconQRCodeTest extends TestCase
{
use HandlesDataUri;
protected IQRCodeProvider $qr;
protected function setUp(): void
{
$this->qr = new BaconQrCodeProvider(1, '#000', '#FFF', 'svg');
;
}
public function testDependency(): void
{
$qr = new BaconQrCodeProvider(1, '#000', '#FFF', 'svg');
$tfa = new TwoFactorAuth('Test&Issuer', 6, 30, Algorithm::Sha1, $qr);
$tfa = new TwoFactorAuth($this->qr, 'Test&Issuer', 6, 30, Algorithm::Sha1);
$data = $this->DecodeDataUri($tfa->getQRCodeImageAsDataUri('Test&Label', 'VMR466AB62ZBOKHE'));
$this->assertSame('image/svg+xml', $data['mimetype']);
+1 -1
View File
@@ -17,7 +17,7 @@ class EndroidQRCodeTest extends TestCase
public function testDependency(): void
{
$qr = new EndroidQrCodeProvider();
$tfa = new TwoFactorAuth('Test&Issuer', 6, 30, Algorithm::Sha1, $qr);
$tfa = new TwoFactorAuth($qr, 'Test&Issuer', 6, 30, Algorithm::Sha1);
$data = $this->DecodeDataUri($tfa->getQRCodeImageAsDataUri('Test&Label', 'VMR466AB62ZBOKHE'));
$this->assertSame('image/png', $data['mimetype']);
$this->assertSame('base64', $data['encoding']);