joe před 4 roky
rodič
revize
58faf9d0c5
55 změnil soubory, kde provedl 2820 přidání a 5 odebrání
  1. 2 1
      composer.json
  2. 77 1
      composer.lock
  3. 1 0
      vendor/composer/autoload_psr4.php
  4. 8 0
      vendor/composer/autoload_static.php
  5. 79 0
      vendor/composer/installed.json
  6. 11 2
      vendor/composer/installed.php
  7. 4 0
      vendor/robthree/twofactorauth/.github/FUNDING.yml
  8. 27 0
      vendor/robthree/twofactorauth/.github/workflows/test.yml
  9. 192 0
      vendor/robthree/twofactorauth/.gitignore
  10. 22 0
      vendor/robthree/twofactorauth/LICENSE
  11. 209 0
      vendor/robthree/twofactorauth/README.md
  12. 70 0
      vendor/robthree/twofactorauth/TwoFactorAuth.phpproj
  13. 22 0
      vendor/robthree/twofactorauth/TwoFactorAuth.sln
  14. 49 0
      vendor/robthree/twofactorauth/composer.json
  15. 35 0
      vendor/robthree/twofactorauth/demo/demo.php
  16. 50 0
      vendor/robthree/twofactorauth/demo/loader.php
  17. 152 0
      vendor/robthree/twofactorauth/lib/Providers/Qr/BaconQrCodeProvider.php
  18. 33 0
      vendor/robthree/twofactorauth/lib/Providers/Qr/BaseHTTPQRCodeProvider.php
  19. 70 0
      vendor/robthree/twofactorauth/lib/Providers/Qr/EndroidQrCodeProvider.php
  20. 35 0
      vendor/robthree/twofactorauth/lib/Providers/Qr/EndroidQrCodeWithLogoProvider.php
  21. 24 0
      vendor/robthree/twofactorauth/lib/Providers/Qr/IQRCodeProvider.php
  22. 60 0
      vendor/robthree/twofactorauth/lib/Providers/Qr/ImageChartsQRCodeProvider.php
  23. 7 0
      vendor/robthree/twofactorauth/lib/Providers/Qr/QRException.php
  24. 108 0
      vendor/robthree/twofactorauth/lib/Providers/Qr/QRServerProvider.php
  25. 76 0
      vendor/robthree/twofactorauth/lib/Providers/Qr/QRicketProvider.php
  26. 22 0
      vendor/robthree/twofactorauth/lib/Providers/Rng/CSRNGProvider.php
  27. 43 0
      vendor/robthree/twofactorauth/lib/Providers/Rng/HashRNGProvider.php
  28. 18 0
      vendor/robthree/twofactorauth/lib/Providers/Rng/IRNGProvider.php
  29. 37 0
      vendor/robthree/twofactorauth/lib/Providers/Rng/MCryptRNGProvider.php
  30. 40 0
      vendor/robthree/twofactorauth/lib/Providers/Rng/OpenSSLRNGProvider.php
  31. 7 0
      vendor/robthree/twofactorauth/lib/Providers/Rng/RNGException.php
  32. 71 0
      vendor/robthree/twofactorauth/lib/Providers/Time/HttpTimeProvider.php
  33. 11 0
      vendor/robthree/twofactorauth/lib/Providers/Time/ITimeProvider.php
  34. 11 0
      vendor/robthree/twofactorauth/lib/Providers/Time/LocalMachineTimeProvider.php
  35. 70 0
      vendor/robthree/twofactorauth/lib/Providers/Time/NTPTimeProvider.php
  36. 7 0
      vendor/robthree/twofactorauth/lib/Providers/Time/TimeException.php
  37. 360 0
      vendor/robthree/twofactorauth/lib/TwoFactorAuth.php
  38. 7 0
      vendor/robthree/twofactorauth/lib/TwoFactorAuthException.php
  39. binární
      vendor/robthree/twofactorauth/logo.png
  40. binární
      vendor/robthree/twofactorauth/multifactorauthforeveryone.png
  41. 26 0
      vendor/robthree/twofactorauth/phpunit.xml
  42. 26 0
      vendor/robthree/twofactorauth/tests/MightNotMakeAssertions.php
  43. 56 0
      vendor/robthree/twofactorauth/tests/Providers/Qr/IQRCodeProviderTest.php
  44. 24 0
      vendor/robthree/twofactorauth/tests/Providers/Qr/TestQrProvider.php
  45. 30 0
      vendor/robthree/twofactorauth/tests/Providers/Rng/CSRNGProviderTest.php
  46. 24 0
      vendor/robthree/twofactorauth/tests/Providers/Rng/HashRNGProviderTest.php
  47. 61 0
      vendor/robthree/twofactorauth/tests/Providers/Rng/IRNGProviderTest.php
  48. 32 0
      vendor/robthree/twofactorauth/tests/Providers/Rng/MCryptRNGProviderTest.php
  49. 9 0
      vendor/robthree/twofactorauth/tests/Providers/Rng/NeedsRngLengths.php
  50. 37 0
      vendor/robthree/twofactorauth/tests/Providers/Rng/OpenSSLRNGProviderTest.php
  51. 41 0
      vendor/robthree/twofactorauth/tests/Providers/Rng/TestRNGProvider.php
  52. 53 0
      vendor/robthree/twofactorauth/tests/Providers/Time/ITimeProviderTest.php
  53. 27 0
      vendor/robthree/twofactorauth/tests/Providers/Time/TestTimeProvider.php
  54. 246 0
      vendor/robthree/twofactorauth/tests/TwoFactorAuthTest.php
  55. 1 1
      vendor/services.php

+ 2 - 1
composer.json

@@ -41,7 +41,8 @@
         "topthink/think-template": "^2.0",
         "alibabacloud/dysmsapi": "^1.8",
         "alipaysdk/easysdk": "^2.2",
-        "pda/pheanstalk": "^4.0"
+        "pda/pheanstalk": "^4.0",
+        "robthree/twofactorauth": "^1.8"
     },
     "autoload": {
         "psr-4": {

+ 77 - 1
composer.lock

@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "8b32b790d656d038e194b27426d8d507",
+    "content-hash": "a266747a07e4f1873c8dddfde97cadc3",
     "packages": [
         {
             "name": "adbario/php-dot-notation",
@@ -2258,6 +2258,82 @@
             "description": "A polyfill for getallheaders.",
             "time": "2016-02-11T07:05:27+00:00"
         },
+        {
+            "name": "robthree/twofactorauth",
+            "version": "1.8.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/RobThree/TwoFactorAuth.git",
+                "reference": "30a38627ae1e7c9399dae67e265063cd6ec5276c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/30a38627ae1e7c9399dae67e265063cd6ec5276c",
+                "reference": "30a38627ae1e7c9399dae67e265063cd6ec5276c",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.6.0"
+            },
+            "require-dev": {
+                "php-parallel-lint/php-parallel-lint": "^1.2",
+                "phpunit/phpunit": "@stable"
+            },
+            "suggest": {
+                "bacon/bacon-qr-code": "Needed for BaconQrCodeProvider provider",
+                "endroid/qr-code": "Needed for EndroidQrCodeProvider"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "RobThree\\Auth\\": "lib"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Rob Janssen",
+                    "homepage": "http://robiii.me",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Two Factor Authentication",
+            "homepage": "https://github.com/RobThree/TwoFactorAuth",
+            "keywords": [
+                "Authentication",
+                "MFA",
+                "Multi Factor Authentication",
+                "Two Factor Authentication",
+                "authenticator",
+                "authy",
+                "php",
+                "tfa"
+            ],
+            "support": {
+                "issues": "https://github.com/RobThree/TwoFactorAuth/issues",
+                "source": "https://github.com/RobThree/TwoFactorAuth"
+            },
+            "funding": [
+                {
+                    "url": "https://paypal.me/robiii",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/RobThree",
+                    "type": "github"
+                }
+            ],
+            "time": "2021-03-09T18:24:05+00:00"
+        },
         {
             "name": "songshenzong/support",
             "version": "2.0.5",

+ 1 - 0
vendor/composer/autoload_psr4.php

@@ -30,6 +30,7 @@ return array(
     'Stringy\\' => array($vendorDir . '/danielstjules/stringy/src'),
     'Spatie\\Macroable\\' => array($vendorDir . '/spatie/macroable/src'),
     'Songshenzong\\Support\\' => array($vendorDir . '/songshenzong/support/src'),
+    'RobThree\\Auth\\' => array($vendorDir . '/robthree/twofactorauth/lib'),
     'Qiniu\\' => array($vendorDir . '/qiniu/php-sdk/src/Qiniu'),
     'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
     'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),

+ 8 - 0
vendor/composer/autoload_static.php

@@ -75,6 +75,10 @@ class ComposerStaticInitf16474ac994ccc25392f403933800b79
             'Spatie\\Macroable\\' => 17,
             'Songshenzong\\Support\\' => 21,
         ),
+        'R' => 
+        array (
+            'RobThree\\Auth\\' => 14,
+        ),
         'Q' => 
         array (
             'Qiniu\\' => 6,
@@ -241,6 +245,10 @@ class ComposerStaticInitf16474ac994ccc25392f403933800b79
         array (
             0 => __DIR__ . '/..' . '/songshenzong/support/src',
         ),
+        'RobThree\\Auth\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/robthree/twofactorauth/lib',
+        ),
         'Qiniu\\' => 
         array (
             0 => __DIR__ . '/..' . '/qiniu/php-sdk/src/Qiniu',

+ 79 - 0
vendor/composer/installed.json

@@ -2360,6 +2360,85 @@
             "description": "A polyfill for getallheaders.",
             "install-path": "../ralouphie/getallheaders"
         },
+        {
+            "name": "robthree/twofactorauth",
+            "version": "1.8.0",
+            "version_normalized": "1.8.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/RobThree/TwoFactorAuth.git",
+                "reference": "30a38627ae1e7c9399dae67e265063cd6ec5276c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/30a38627ae1e7c9399dae67e265063cd6ec5276c",
+                "reference": "30a38627ae1e7c9399dae67e265063cd6ec5276c",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.6.0"
+            },
+            "require-dev": {
+                "php-parallel-lint/php-parallel-lint": "^1.2",
+                "phpunit/phpunit": "@stable"
+            },
+            "suggest": {
+                "bacon/bacon-qr-code": "Needed for BaconQrCodeProvider provider",
+                "endroid/qr-code": "Needed for EndroidQrCodeProvider"
+            },
+            "time": "2021-03-09T18:24:05+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "RobThree\\Auth\\": "lib"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Rob Janssen",
+                    "homepage": "http://robiii.me",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Two Factor Authentication",
+            "homepage": "https://github.com/RobThree/TwoFactorAuth",
+            "keywords": [
+                "Authentication",
+                "MFA",
+                "Multi Factor Authentication",
+                "Two Factor Authentication",
+                "authenticator",
+                "authy",
+                "php",
+                "tfa"
+            ],
+            "support": {
+                "issues": "https://github.com/RobThree/TwoFactorAuth/issues",
+                "source": "https://github.com/RobThree/TwoFactorAuth"
+            },
+            "funding": [
+                {
+                    "url": "https://paypal.me/robiii",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/RobThree",
+                    "type": "github"
+                }
+            ],
+            "install-path": "../robthree/twofactorauth"
+        },
         {
             "name": "songshenzong/support",
             "version": "2.0.5",

+ 11 - 2
vendor/composer/installed.php

@@ -5,7 +5,7 @@
         'type' => 'project',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
-        'reference' => '75b0b2abd4ffb432dae387387ec6637c1880cd6d',
+        'reference' => '0dc79f4f86328ebd7010950becab8cc4c43c9be8',
         'name' => 'topthink/think',
         'dev' => true,
     ),
@@ -478,6 +478,15 @@
             'reference' => '5601c8a83fbba7ef674a7369456d12f1e0d0eafa',
             'dev_requirement' => false,
         ),
+        'robthree/twofactorauth' => array(
+            'pretty_version' => '1.8.0',
+            'version' => '1.8.0.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../robthree/twofactorauth',
+            'aliases' => array(),
+            'reference' => '30a38627ae1e7c9399dae67e265063cd6ec5276c',
+            'dev_requirement' => false,
+        ),
         'songshenzong/support' => array(
             'pretty_version' => '2.0.5',
             'version' => '2.0.5.0',
@@ -607,7 +616,7 @@
             'type' => 'project',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),
-            'reference' => '75b0b2abd4ffb432dae387387ec6637c1880cd6d',
+            'reference' => '0dc79f4f86328ebd7010950becab8cc4c43c9be8',
             'dev_requirement' => false,
         ),
         'topthink/think-captcha' => array(

+ 4 - 0
vendor/robthree/twofactorauth/.github/FUNDING.yml

@@ -0,0 +1,4 @@
+# These are supported funding model platforms
+
+github: [RobThree]
+custom: ["https://paypal.me/robiii"]

+ 27 - 0
vendor/robthree/twofactorauth/.github/workflows/test.yml

@@ -0,0 +1,27 @@
+name: Test
+
+on:
+  push:
+  pull_request:
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+
+    strategy:
+      matrix:
+        php-version: ['5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0']
+
+    steps:
+    - uses: actions/checkout@v2
+
+    - uses: shivammathur/setup-php@v2
+      with:
+        php-version: ${{ matrix.php-version }}
+        tools: composer
+        coverage: xdebug
+
+    - uses: ramsey/composer-install@v1
+
+    - run: composer lint
+    - run: composer test

+ 192 - 0
vendor/robthree/twofactorauth/.gitignore

@@ -0,0 +1,192 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.sln.docstates
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+build/
+bld/
+[Bb]in/
+[Oo]bj/
+
+# Roslyn cache directories
+*.ide/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+#NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opensdf
+*.sdf
+*.cachefile
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding addin-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# If using the old MSBuild-Integrated Package Restore, uncomment this:
+#!**/packages/repositories.config
+
+# Windows Azure Build Output
+csx/
+*.build.csdef
+
+# Windows Store app package directory
+AppPackages/
+
+# Others
+sql/
+*.Cache
+ClientBin/
+[Ss]tyle[Cc]op.*
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.pfx
+*.publishsettings
+node_modules/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# Composer
+/vendor
+composer.lock
+
+# .vs
+.vs/
+
+.phpunit.result.cache

+ 22 - 0
vendor/robthree/twofactorauth/LICENSE

@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2014-2015 Rob Janssen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+

+ 209 - 0
vendor/robthree/twofactorauth/README.md

@@ -0,0 +1,209 @@
+# ![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")
+
+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.
+
+<p align="center">
+<img src="https://raw.githubusercontent.com/RobThree/TwoFactorAuth/master/multifactorauthforeveryone.png">
+</p>
+
+## Requirements
+
+* Tested on PHP 5.6 up to 8.0
+* [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), [MCrypt](http://php.net/manual/en/book.mcrypt.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:
+
+* [endroid/qr-code](https://github.com/endroid/qr-code) if using `EndroidQrCodeProvider` or `EndroidQrCodeWithLogoProvider`.
+* [bacon/bacon-qr-code](https://github.com/Bacon/BaconQrCode) if using `BaconQrCodeProvider`.
+
+## Installation
+
+Run the following command:
+
+`php composer.phar require robthree/twofactorauth`
+
+## Quick start
+
+If you want to hit the ground running then have a look at the [demo](demo/demo.php). It's very simple and easy!
+
+## Usage
+
+Here are some code snippets that should help you get started...
+
+````php
+// Create a TwoFactorAuth instance
+$tfa = new RobThree\Auth\TwoFactorAuth('My Company');
+````
+
+The TwoFactorAuth class constructor accepts 7 arguments (all optional):
+
+Argument          | Default value | Use
+------------------|---------------|--------------------------------------------------
+`$issuer`         | `null`        | Will be displayed in the app as issuer name
+`$digits`         | `6`           | The number of digits the resulting codes will be
+`$period`         | `30`          | The number of seconds a code will be valid
+`$algorithm`      | `sha1`        | The algorithm used (one of `sha1`, `sha256`, `sha512`, `md5`)
+`$qrcodeprovider` | `null`        | QR-code provider (more on this later)
+`$rngprovider`    | `null`        | Random Number Generator provider (more on this later)
+`$timeprovider`   | `null`        | Time provider (more on this later)
+
+These arguments are all '`write once`'; the class will, for it's lifetime, use these values when generating / calculating codes. The number of digits, the period and algorithm are all set to values Google's Authenticator app uses (and supports). You may specify `8` digits, a period of `45` seconds and the `sha256` algorithm but the authenticator app (be it Google's implementation, Authy or any other app) may or may not support these values. Your mileage may vary; keep it on the safe side if you don't control which app your audience uses.
+
+### Step 1: Set up secret shared key
+
+When a user wants to setup two-factor auth (or, more correctly, multi-factor auth) you need to create a secret. This will be your **shared secret**. This secret will need to be entered by the user in their app. This can be done manually, in which case you simply display the secret and have the user type it in the app:
+
+````php
+$secret = $tfa->createSecret();
+````
+
+The `createSecret()` method accepts two arguments: `$bits` (default: `80`) and `$requirecryptosecure` (default: `true`). The former is the number of bits generated for the shared secret. Make sure this argument is a multiple of 8 and, again, keep in mind that not all combinations may be supported by all apps. Google authenticator seems happy with 80 and 160, the default is set to 80 because that's what most sites (that I know of) currently use; however a value of 160 or higher is recommended (see [RFC 4226 - Algorithm Requirements](https://tools.ietf.org/html/rfc4226#section-4)). The latter is used to ensure that the secret is cryptographically secure; if you don't care very much for cryptographically secure secrets you can specify `false` and use a **non**-cryptographically secure RNG provider.
+
+````php
+// Display shared secret
+<p>Please enter the following code in your app: '<?php echo $secret; ?>'</p>
+````
+
+Another, more user-friendly, way to get the shared secret into the app is to generate a [QR-code](http://en.wikipedia.org/wiki/QR_code) which can be scanned by the app. To generate these QR codes you can use any one of the built-in `QRProvider` classes:
+
+1. `QRServerProvider` (default)
+2. `ImageChartsQRCodeProvider`
+3. `QRicketProvider`
+4. `EndroidQrCodeProvider` (requires `endroid/qr-code` to be installed)
+5. `EndroidQrCodeWithLogoProvider` (same, but supporting embedded images)
+6. `BaconQrCodeProvider` (requires `bacon/bacon-qr-code` to be installed)
+
+...or implement your own provider. To implement your own provider all you need to do is implement the `IQRCodeProvider` interface. You can use the built-in providers mentioned before to serve as an example or read the next chapter in this file. The built-in classes all use a 3rd (e.g. external) party (Image-charts, QRServer and QRicket) for the hard work of generating QR-codes (note: each of these services might at some point not be available or impose limitations to the number of codes generated per day, hour etc.). You could, however, easily use a project like [PHP QR Code](http://phpqrcode.sourceforge.net/) (or one of the [many others](https://packagist.org/search/?q=qr)) to generate your QR-codes without depending on external sources. Later on we'll [demonstrate](#qr-code-providers) how to do this.
+
+The built-in providers all have some provider-specific 'tweaks' you can 'apply'. Some provide support for different colors, others may let you specify the desired image-format etc. What they all have in common is that they return a QR-code as binary blob which, in turn, will be turned into a [data URI](http://en.wikipedia.org/wiki/Data_URI_scheme) by the `TwoFactorAuth` class. This makes it easy for you to display the image without requiring extra 'roundtrips' from browser to server and vice versa.
+
+````php
+// Display QR code to user
+<p>Scan the following image with your app:</p>
+<p><img src="<?php echo $tfa->getQRCodeImageAsDataUri('Bob Ross', $secret); ?>"></p>
+````
+
+When outputting a QR-code you can choose a `$label` for the user (which, when entering a shared secret manually, will have to be chosen by the user). This label may be an empty string or `null`. Also a `$size` may be specified (in pixels, width == height) for which we use a default value of `200`.
+
+### Step 2: Verify secret shared key
+
+When the shared secret is added to the app, the app will be ready to start generating codes which 'expire' each '`$period`' number of seconds. To make sure the secret was entered, or scanned, correctly you need to verify this by having the user enter a generated code. To check if the generated code is valid you call the `verifyCode()` method:
+
+````php
+// Verify code
+$result = $tfa->verifyCode($_SESSION['secret'], $_POST['verification']);
+````
+
+If you do extra validations with your `$_POST` values, just make sure the code is still submitted as string - even if that's a numeric code, casting it to integer is unreliable. Also, you may need to store `$secret` in a `$_SESSION` or other persistent storage between requests. `verifyCode()` will return either `true` (the code was valid) or `false` (the code was invalid; no points for you!).
+
+ The `verifyCode()` accepts, aside from `$secret` and `$code`, three more arguments, with the first being `$discrepancy`. Since TOTP codes are based on time("slices") it is very important that the server (but also client) have a correct date/time. But because the two *may* differ a bit we usually allow a certain amount of leeway. Because generated codes are valid for a specific period (remember the `$period` argument in the `TwoFactorAuth`'s constructor?) we usually check the period directly before and the period directly after the current time when validating codes. So when the current time is `14:34:21`, which results in a 'current timeslice' of `14:34:00` to `14:34:30` we also calculate/verify the codes for `14:33:30` to `14:34:00` and for `14:34:30` to `14:35:00`. This gives us a 'window' of `14:33:30` to `14:35:00`. The `$discrepancy` argument specifies how many periods (or: timeslices) we check in either direction of the current time. The default `$discrepancy` of `1` results in (max.) 3 period checks: -1, current and +1 period. A `$discrepancy` of `4` would result in a larger window (or: bigger time difference between client and server) of -4, -3, -2, -1, current, +1, +2, +3 and +4 periods.
+
+The second, `$time`, allows you to check a code for a specific point in time. This argument has no real practical use but can be handy for unittesting etc. The default value, `null`, means: use the current time.
+
+The third, `$timeslice`, is an out-argument; the value returned in `$timeslice` is the value of the timeslice that matched the code (if any). This value will be 0 when the code doesn't match and non-zero when the code matches. This value can be stored with the user and can be used to prevent replay-attacks. All you need to do is, on successful login, make sure `$timeslice` is greater than the previously stored timeslice.
+
+### Step 3: Store `$secret` with user and we're done!
+
+Ok, so now the code has been verified and found to be correct. Now we can store the `$secret` with our user in our database (or elsewhere) and whenever the user begins a new session we ask for a code generated by the authentication app of their choice. All we need to do is call `verifyCode()` again with the shared secret and the entered code and we know if the user is legit or not.
+
+Simple as 1-2-3.
+
+All we need is 3 methods and a constructor:
+
+````php
+public function __construct(
+    $issuer = null,
+    $digits = 6,
+    $period = 30,
+    $algorithm = 'sha1',
+    RobThree\Auth\Providers\Qr\IQRCodeProvider $qrcodeprovider = null,
+    RobThree\Auth\Providers\Rng\IRNGProvider $rngprovider = null
+);
+public function createSecret($bits = 80, $requirecryptosecure = true): string;
+public function getQRCodeImageAsDataUri($label, $secret, $size = 200): string;
+public function verifyCode($secret, $code, $discrepancy = 1, $time = null): bool;
+````
+
+### QR-code providers
+
+As mentioned before, this library comes with five 'built-in' QR-code providers. This chapter will touch the subject a bit but most of it should be self-explanatory. The `TwoFactorAuth`-class accepts a `$qrcodeprovider` argument which lets you specify a built-in or custom QR-code provider. All five built-in providers do a simple HTTP request to retrieve an image using cURL and implement the [`IQRCodeProvider`](lib/Providers/Qr/IQRCodeProvider.php) interface which is all you need to implement to write your own QR-code provider.
+
+The default provider is the [`QRServerProvider`](lib/Providers/Qr/QRServerProvider.php) which uses the [goqr.me API](http://goqr.me/api/doc/create-qr-code/) to render QR-codes. Then we have the [`ImageChartsQRCodeProvider`](lib/Providers/Qr/ImageChartsQRCodeProvider.php) which uses the [image-charts.com replacement for Google Image Charts](https://image-charts.com) to render QR-codes and the [`QRicketProvider`](lib/Providers/Qr/QRicketProvider.php) which uses the [QRickit API](http://qrickit.com/qrickit_apps/qrickit_api.php). These three providers all inherit from a common (abstract) baseclass named [`BaseHTTPQRCodeProvider`](lib/Providers/Qr/BaseHTTPQRCodeProvider.php) because all three share the same functionality: retrieve an image from a 3rd party over HTTP. Finally, we have [`EndroidQrCodeProvider`](lib/Providers/Qr/EndroidQrCodeProvider.php), [`EndroidQrCodeWithLogoProvider`](lib/Providers/Qr/EndroidQrCodeWithLogoProvider.php) and [`BaconQrCodeProvider`](lib/Providers/Qr/BaconQrCodeProvider.php) which require an optional dependency to be installed to use (see Requirements section above), but will generate the QR codes locally. All five classes have constructors that allow you to tweak some settings and most, if not all, arguments should speak for themselves. If you're not sure which values are supported, click the links in this paragraph for documentation on the API's that are utilized by these classes.
+
+If you don't like any of the built-in classes because you don't want to rely on external resources for example or because you're paranoid about sending the TOTP secret to these 3rd parties (which is useless to them since they miss *at least one* other factor in the [MFA process](http://en.wikipedia.org/wiki/Multi-factor_authentication)), feel tree to implement your own. The `IQRCodeProvider` interface couldn't be any simpler. All you need to do is implement 2 methods:
+
+````php
+getMimeType();
+getQRCodeImage($qrtext, $size);
+````
+
+The `getMimeType()` method should return the [MIME type](http://en.wikipedia.org/wiki/Internet_media_type) of the image that is returned by our implementation of `getQRCodeImage()`. In this example it's simply `image/png`. The `getQRCodeImage()` method is passed two arguments: `$qrtext` and `$size`. The latter, `$size`, is simply the width/height in pixels of the image desired by the caller. The first, `$qrtext` is the text that should be encoded in the QR-code. An example of such a text would be:
+
+`otpauth://totp/LABEL:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=ISSUER`
+
+All you need to do is return the QR-code as binary image data and you're done. All parts of the `$qrtext` have been escaped for you (but note: you *may* need to escape the entire `$qrtext` just once more when passing the data to another server as GET-argument).
+
+Let's see if we can use [PHP QR Code](http://phpqrcode.sourceforge.net/) to implement our own, custom, no-3rd-parties-allowed-here, provider. We start with downloading the [required (single) file](https://github.com/t0k4rt/phpqrcode/blob/master/phpqrcode.php) and putting it in the directory where `TwoFactorAuth.php` is located as well. Now let's implement the provider: create another file named `myprovider.php` in the `Providers\Qr` directory and paste in this content:
+
+````php
+<?php
+require_once '../../phpqrcode.php';                 // Yeah, we're gonna need that
+
+namespace RobThree\Auth\Providers\Qr;
+
+class MyProvider implements IQRCodeProvider {
+  public function getMimeType() {
+    return 'image/png';                             // This provider only returns PNG's
+  }
+
+  public function getQRCodeImage($qrtext, $size) {
+    ob_start();                                     // 'Catch' QRCode's output
+    QRCode::png($qrtext, null, QR_ECLEVEL_L, 3, 4); // We ignore $size and set it to 3
+                                                    // since phpqrcode doesn't support
+                                                    // a size in pixels...
+    $result = ob_get_contents();                    // 'Catch' QRCode's output
+    ob_end_clean();                                 // Cleanup
+    return $result;                                 // Return image
+  }
+}
+````
+
+That's it. We're done! We've implemented our own provider (with help of PHP QR Code). No more external dependencies, no more unnecessary latencies. Now let's *use* our provider:
+
+````php
+<?php
+$mp = new RobThree\Auth\Providers\Qr\MyProvider();
+$tfa = new RobThree\Auth\TwoFactorAuth('My Company', 6, 30, 'sha1', $mp);
+$secret = $tfa->createSecret();
+?>
+<p><img src="<?php echo $tfa->getQRCodeImageAsDataUri('Bob Ross', $secret); ?>"></p>
+````
+
+Voilà. Couldn't make it any simpler.
+
+### RNG providers
+
+This library also comes with three 'built-in' RNG providers ([Random Number Generator](https://en.wikipedia.org/wiki/Random_number_generation)). 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. It will, by default, try to use the [`CSRNGProvider`](lib/Providers/Rng/CSRNGProvider.php) for PHP7+ or the [`MCryptRNGProvider`](lib/Providers/Rng/MCryptRNGProvider.php); if this is not available/supported for any reason it will try to use the [`OpenSSLRNGProvider`](lib/Providers/Rng/OpenSSLRNGProvider.php) and if that is also not available/supported it will try to use the final RNG provider: [`HashRNGProvider`](lib/Providers/Rng/HashRNGProvider.php). Each of these providers use their own method of generating a random sequence of bytes. The first three (`CSRNGProvider`, `OpenSSLRNGProvider` and `MCryptRNGProvider`) return a [cryptographically secure](https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator) sequence of random bytes whereas the `HashRNGProvider` returns a **non-cryptographically secure** sequence.
+
+You can easily implement your own `RNGProvider` by simply implementing the `IRNGProvider` interface. Each of the 'built-in' RNG providers have some constructor arguments that allow you to 'tweak' some of the settings to use when creating the random bytes such as which source to use (`MCryptRNGProvider`) or which hashing algorithm (`HashRNGProvider`). I encourage you to have a look at some of the ['built-in' RNG providers](lib/Providers/Rng) for details and the [`IRNGProvider` interface](lib/Providers/Rng/IRNGProvider.php).
+
+### Time providers
+
+Another set of providers in this library are the Time Providers; this library provides three 'built-in' ones. The default Time Provider used is the [`LocalMachineTimeProvider`](lib/Providers/Time/LocalMachineTimeProvider.php); this provider simply returns the output of `Time()` and is *highly recommended* as default provider. The [`HttpTimeProvider`](lib/Providers/Time/HttpTimeProvider.php) executes a `HEAD` request against a given webserver (default: google.com) and tries to extract the `Date:`-HTTP header and returns it's date. Other url's/domains can be used by specifying the url in the constructor. The final Time Provider is the [`NTPTimeProvider`](lib/Providers/Time/NTPTimeProvider.php) which does an NTP request to a specified NTP server.
+
+You can easily implement your own `TimeProvider` by simply implementing the `ITimeProvider` interface.
+
+As to *why* these Time Providers are implemented: it allows the TwoFactorAuth library to ensure the hosts time is correct (or rather: within a margin). You can use the `ensureCorrectTime()` method to ensure the hosts time is correct. By default this method will compare the hosts time (returned by calling `time()` on the `LocalMachineTimeProvider`) to the default `NTPTimeProvider` and `HttpTimeProvider`. You can pass an array of `ITimeProvider`s to change this and specify the `leniency` (second argument) allowed (default: 5 seconds). The method will throw when the TwoFactorAuth's timeprovider (which can be any `ITimeProvider`, see constructor) differs more than the given amount of seconds from any of the given `ITimeProviders`. We advise to call this method sparingly when relying on 3rd parties (which both the `HttpTimeProvider` and `NTPTimeProvider` do) or, if you need to ensure time is correct on a (very) regular basis to implement an `ITimeProvider` that is more efficient than the 'built-in' ones (like use a GPS signal). The `ensureCorrectTime()` method is mostly to be used to make sure the server is configured correctly.
+
+## Integrations
+
+- [CakePHP 3](https://github.com/andrej-griniuk/cakephp-two-factor-auth)
+
+## License
+
+Licensed under MIT license. See [LICENSE](https://raw.githubusercontent.com/RobThree/TwoFactorAuth/master/LICENSE) for details.
+
+[Logo / icon](http://www.iconmay.com/Simple/Travel_and_Tourism_Part_2/luggage_lock_safety_baggage_keys_cylinder_lock_hotel_travel_tourism_luggage_lock_icon_465) under  CC0 1.0 Universal (CC0 1.0) Public Domain Dedication  ([Archived page](http://riii.nl/tm7ap))

+ 70 - 0
vendor/robthree/twofactorauth/TwoFactorAuth.phpproj

@@ -0,0 +1,70 @@
+<?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>7.0</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\MCryptRNGProvider.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 - 0
vendor/robthree/twofactorauth/TwoFactorAuth.sln

@@ -0,0 +1,22 @@
+
+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

+ 49 - 0
vendor/robthree/twofactorauth/composer.json

@@ -0,0 +1,49 @@
+{
+    "name": "robthree/twofactorauth",
+    "description": "Two Factor Authentication",
+    "version": "1.8.0",
+    "type": "library",
+    "keywords": [ "Authentication", "Two Factor Authentication", "Multi Factor Authentication", "TFA", "MFA", "PHP", "Authenticator", "Authy" ],
+    "homepage": "https://github.com/RobThree/TwoFactorAuth",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Rob Janssen",
+            "homepage": "http://robiii.me",
+            "role": "Developer"
+        }
+    ],
+    "support": {
+        "issues": "https://github.com/RobThree/TwoFactorAuth/issues",
+        "source": "https://github.com/RobThree/TwoFactorAuth"
+    },
+    "require": {
+        "php": ">=5.6.0"
+    },
+    "require-dev": {
+        "phpunit/phpunit": "@stable",
+        "php-parallel-lint/php-parallel-lint": "^1.2"
+    },
+    "suggest": {
+        "bacon/bacon-qr-code": "Needed for BaconQrCodeProvider provider",
+        "endroid/qr-code": "Needed for EndroidQrCodeProvider"
+    },
+    "autoload": {
+        "psr-4": {
+            "RobThree\\Auth\\": "lib"
+        }
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "Tests\\": "tests/"
+        }
+    },
+    "scripts": {
+        "lint": [
+            "parallel-lint --exclude vendor ."
+        ],
+        "test": [
+            "XDEBUG_MODE=coverage phpunit"
+        ]
+    }
+}

+ 35 - 0
vendor/robthree/twofactorauth/demo/demo.php

@@ -0,0 +1,35 @@
+<!doctype html>
+<html>
+<head>
+    <title>Demo</title>
+</head>
+<body>
+    <ol>
+        <?php
+        require_once 'loader.php';
+        Loader::register('../lib','RobThree\\Auth');
+
+        use \RobThree\Auth\TwoFactorAuth;
+
+        $tfa = new TwoFactorAuth('MyApp');
+
+        echo '<li>First create a secret and associate it with a user';
+        $secret = $tfa->createSecret(160);  // Though the default is an 80 bits secret (for backwards compatibility reasons) we recommend creating 160+ bits secrets (see RFC 4226 - Algorithm Requirements)
+        echo '<li>Next create a QR code and let the user scan it:<br><img src="' . $tfa->getQRCodeImageAsDataUri('My label', $secret) . '"><br>...or display the secret to the user for manual entry: ' . chunk_split($secret, 4, ' ');
+        $code = $tfa->getCode($secret);
+        echo '<li>Next, have the user verify the code; at this time the code displayed by a 2FA-app would be: <span style="color:#00c">' . $code . '</span> (but that changes periodically)';
+        echo '<li>When the code checks out, 2FA can be / is enabled; store (encrypted?) secret with user and have the user verify a code each time a new session is started.';
+        echo '<li>When aforementioned code (' . $code . ') was entered, the result would be: ' . (($tfa->verifyCode($secret, $code) === true) ? '<span style="color:#0c0">OK</span>' : '<span style="color:#c00">FAIL</span>');
+        ?>
+    </ol>
+    <p>Note: Make sure your server-time is <a href="http://en.wikipedia.org/wiki/Network_Time_Protocol">NTP-synced</a>! Depending on the $discrepancy allowed your time cannot drift too much from the users' time!</p>
+    <?php
+    try {
+        $tfa->ensureCorrectTime();
+        echo 'Your hosts time seems to be correct / within margin';
+    } catch (RobThree\Auth\TwoFactorAuthException $ex) {
+        echo '<b>Warning:</b> Your hosts time seems to be off: ' . $ex->getMessage();
+    }
+    ?>
+</body>
+</html>

+ 50 - 0
vendor/robthree/twofactorauth/demo/loader.php

@@ -0,0 +1,50 @@
+<?php
+
+//http://www.leaseweblabs.com/2014/04/psr-0-psr-4-autoloading-classes-php/
+class Loader
+{
+    protected static $parentPath = null;
+    protected static $paths = null;
+    protected static $files = null;
+    protected static $nsChar = '\\';
+    protected static $initialized = false;
+    
+    protected static function initialize()
+    {
+        if (static::$initialized) return;
+        static::$initialized = true;
+        static::$parentPath = __FILE__;
+        for ($i=substr_count(get_class(), static::$nsChar);$i>=0;$i--) {
+            static::$parentPath = dirname(static::$parentPath);
+        }
+        static::$paths = array();
+        static::$files = array(__FILE__);
+    }
+    
+    public static function register($path,$namespace) {
+        if (!static::$initialized) static::initialize();
+        static::$paths[$namespace] = trim($path,DIRECTORY_SEPARATOR);
+    }
+    
+    public static function load($class) {
+        if (class_exists($class,false)) return;
+        if (!static::$initialized) static::initialize();
+        
+        foreach (static::$paths as $namespace => $path) {
+            if (!$namespace || $namespace.static::$nsChar === substr($class, 0, strlen($namespace.static::$nsChar))) {
+                
+                $fileName = substr($class,strlen($namespace.static::$nsChar)-1);
+                $fileName = str_replace(static::$nsChar, DIRECTORY_SEPARATOR, ltrim($fileName,static::$nsChar));
+                $fileName = static::$parentPath.DIRECTORY_SEPARATOR.$path.DIRECTORY_SEPARATOR.$fileName.'.php';
+                
+                if (file_exists($fileName)) {
+                    include $fileName;
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+}
+
+spl_autoload_register(array('Loader', 'load'));

+ 152 - 0
vendor/robthree/twofactorauth/lib/Providers/Qr/BaconQrCodeProvider.php

@@ -0,0 +1,152 @@
+<?php
+
+namespace RobThree\Auth\Providers\Qr;
+
+use BaconQrCode\Writer;
+use BaconQrCode\Renderer\ImageRenderer;
+use BaconQrCode\Renderer\RendererStyle\RendererStyle;
+use BaconQrCode\Renderer\RendererStyle\Fill;
+use BaconQrCode\Renderer\Color\Rgb;
+use BaconQrCode\Renderer\RendererStyle\EyeFill;
+
+use BaconQrCode\Renderer\Image\EpsImageBackEnd;
+use BaconQrCode\Renderer\Image\ImageBackEndInterface;
+use BaconQrCode\Renderer\Image\ImagickImageBackEnd;
+use BaconQrCode\Renderer\Image\SvgImageBackEnd;
+
+class BaconQrCodeProvider implements IQRCodeProvider
+{
+    private $borderWidth = 4; // default from Bacon QR Code
+    private $backgroundColour;
+    private $foregroundColour;
+    private $format;
+
+    /**
+     * Ensure we using the latest Bacon QR Code and specify default options
+     *
+     * @param int $borderWidth space around the QR code, 4 is the default from Bacon QR Code
+     * @param string $backgroundColour hex reference for the background colour
+     * @param string $foregroundColour hex reference for the foreground colour
+     * @param string $format the desired output, png or svg
+     */
+    public function __construct($borderWidth = 4, $backgroundColour = '#ffffff', $foregroundColour = '#000000', $format = 'png')
+    {
+        if (! class_exists(ImagickImageBackEnd::class)) {
+            throw new \RuntimeException('Make sure you are using version 2 of Bacon QR Code');
+        }
+
+        $this->borderWidth = $borderWidth;
+        $this->backgroundColour = $this->handleColour($backgroundColour);
+        $this->foregroundColour = $this->handleColour($foregroundColour);
+        $this->format = strtolower($format);
+    }
+
+    /**
+     * Standard functions from IQRCodeProvider
+     */
+
+    public function getMimeType()
+    {
+        switch ($this->format) {
+            case 'png':
+                return 'image/png';
+            case 'gif':
+                return 'image/gif';
+            case 'jpg':
+            case 'jpeg':
+                return 'image/jpeg';
+            case 'svg':
+                return 'image/svg+xml';
+            case 'eps':
+                return 'application/postscript';
+        }
+
+        throw new \RuntimeException(sprintf('Unknown MIME-type: %s', $this->format));
+    }
+
+    public function getQRCodeImage($qrText, $size)
+    {
+        switch ($this->format) {
+            case 'svg':
+                $backend = new SvgImageBackEnd;
+                break;
+            case 'eps':
+                $backend = new EpsImageBackEnd;
+                break;
+            default:
+                $backend = new ImagickImageBackEnd($this->format);
+        }
+
+        $output = $this->getQRCodeByBackend($qrText, $size, $backend);
+
+        if ($this->format == 'svg') {
+            $svg = explode("\n", $output);
+            return $svg[1];
+        }
+
+        return $output;
+    }
+
+    /**
+     * Abstract QR code generation function
+     * providing colour changing support
+     */
+    private function getQRCodeByBackend($qrText, $size, ImageBackEndInterface $backend)
+    {
+        $rendererStyleArgs = array($size, $this->borderWidth);
+
+        if (is_array($this->foregroundColour) && is_array($this->backgroundColour)) {
+            $rendererStyleArgs = array_merge($rendererStyleArgs, array(
+                null,
+                null,
+                Fill::withForegroundColor(
+                    new Rgb(...$this->backgroundColour),
+                    new Rgb(...$this->foregroundColour),
+                    new EyeFill(null, null),
+                    new EyeFill(null, null),
+                    new EyeFill(null, null)
+                )
+            ));
+        }
+
+        $writer = new Writer(new ImageRenderer(
+            new RendererStyle(...$rendererStyleArgs),
+            $backend
+        ));
+
+        return $writer->writeString($qrText);
+    }
+
+    /**
+     * Ensure colour is an array of three values but also
+     * accept a string and assume its a 3 or 6 character hex
+     */
+    private function handleColour($colour)
+    {
+        if (is_string($colour) && $colour[0] == '#') {
+            $hexToRGB = function ($input) {
+                // split the array into three chunks
+                $split = str_split(trim($input, '#'), strlen($input) / 3);
+
+                // cope with three character hex reference
+                // three characters plus a # = 4
+                if (strlen($input) == 4) {
+                    array_walk($split, function (&$character) {
+                        $character = str_repeat($character, 2);
+                    });
+                }
+
+                // convert hex to rgb
+                return array_map('hexdec', $split);
+            };
+
+            return $hexToRGB($colour);
+        }
+
+        if (is_array($colour) && count($colour) == 3) {
+            return $colour;
+        }
+
+        throw new \RuntimeException('Invalid colour value');
+    }
+}

+ 33 - 0
vendor/robthree/twofactorauth/lib/Providers/Qr/BaseHTTPQRCodeProvider.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace RobThree\Auth\Providers\Qr;
+
+abstract class BaseHTTPQRCodeProvider implements IQRCodeProvider
+{
+    /** @var bool */
+    protected $verifyssl;
+
+    /**
+     * @param string $url
+     *
+     * @return string|bool
+     */
+    protected function getContent($url)
+    {
+        $curlhandle = curl_init();
+
+        curl_setopt_array($curlhandle, array(
+            CURLOPT_URL => $url,
+            CURLOPT_RETURNTRANSFER => true,
+            CURLOPT_CONNECTTIMEOUT => 10,
+            CURLOPT_DNS_CACHE_TIMEOUT => 10,
+            CURLOPT_TIMEOUT => 10,
+            CURLOPT_SSL_VERIFYPEER => $this->verifyssl,
+            CURLOPT_USERAGENT => 'TwoFactorAuth'
+        ));
+        $data = curl_exec($curlhandle);
+
+        curl_close($curlhandle);
+        return $data;
+    }
+}

+ 70 - 0
vendor/robthree/twofactorauth/lib/Providers/Qr/EndroidQrCodeProvider.php

@@ -0,0 +1,70 @@
+<?php
+namespace RobThree\Auth\Providers\Qr;
+
+use Endroid\QrCode\ErrorCorrectionLevel;
+use Endroid\QrCode\QrCode;
+
+class EndroidQrCodeProvider implements IQRCodeProvider
+{
+    public $bgcolor;
+    public $color;
+    public $margin;
+    public $errorcorrectionlevel;
+
+    public function __construct($bgcolor = 'ffffff', $color = '000000', $margin = 0, $errorcorrectionlevel = 'H')
+    {
+        $this->bgcolor = $this->handleColor($bgcolor);
+        $this->color = $this->handleColor($color);
+        $this->margin = $margin;
+        $this->errorcorrectionlevel = $this->handleErrorCorrectionLevel($errorcorrectionlevel);
+    }
+
+    public function getMimeType()
+    {
+        return 'image/png';
+    }
+
+    public function getQRCodeImage($qrtext, $size)
+    {
+        return $this->qrCodeInstance($qrtext, $size)->writeString();
+    }
+
+    protected function qrCodeInstance($qrtext, $size)
+    {
+        $qrCode = new QrCode($qrtext);
+        $qrCode->setSize($size);
+
+        $qrCode->setErrorCorrectionLevel($this->errorcorrectionlevel);
+        $qrCode->setMargin($this->margin);
+        $qrCode->setBackgroundColor($this->bgcolor);
+        $qrCode->setForegroundColor($this->color);
+
+        return $qrCode;
+    }
+
+    private function handleColor($color)
+    {
+        $split = str_split($color, 2);
+        $r = hexdec($split[0]);
+        $g = hexdec($split[1]);
+        $b = hexdec($split[2]);
+
+        return ['r' => $r, 'g' => $g, 'b' => $b, 'a' => 0];
+    }
+
+    private function handleErrorCorrectionLevel($level)
+    {
+        switch ($level) {
+            case 'L':
+                return ErrorCorrectionLevel::LOW();
+            case 'M':
+                return ErrorCorrectionLevel::MEDIUM();
+            case 'Q':
+                return ErrorCorrectionLevel::QUARTILE();
+            case 'H':
+                return ErrorCorrectionLevel::HIGH();
+            default:
+                return ErrorCorrectionLevel::HIGH();
+        }
+    }
+}

+ 35 - 0
vendor/robthree/twofactorauth/lib/Providers/Qr/EndroidQrCodeWithLogoProvider.php

@@ -0,0 +1,35 @@
+<?php
+namespace RobThree\Auth\Providers\Qr;
+
+use Endroid\QrCode\ErrorCorrectionLevel;
+use Endroid\QrCode\QrCode;
+
+class EndroidQrCodeWithLogoProvider extends EndroidQrCodeProvider
+{
+    protected $logoPath;
+    protected $logoSize;
+
+    /**
+     * Adds an image to the middle of the QR Code.
+     * @param string $path Path to an image file
+     * @param array|int $size Just the width, or [width, height]
+     */
+    public function setLogo($path, $size = null)
+    {
+        $this->logoPath = $path;
+        $this->logoSize = (array)$size;
+    }
+
+    protected function qrCodeInstance($qrtext, $size) {
+        $qrCode = parent::qrCodeInstance($qrtext, $size);
+
+        if ($this->logoPath) {
+            $qrCode->setLogoPath($this->logoPath);
+            if ($this->logoSize) {
+                $qrCode->setLogoSize($this->logoSize[0], $this->logoSize[1]);
+            }
+        }
+
+        return $qrCode;
+    }
+}

+ 24 - 0
vendor/robthree/twofactorauth/lib/Providers/Qr/IQRCodeProvider.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace RobThree\Auth\Providers\Qr;
+
+interface IQRCodeProvider
+{
+    /**
+     * Generate and return the QR code to embed in a web page
+     *
+     * @param string $qrtext the value to encode in the QR code
+     * @param int $size the desired size of the QR code
+     *
+     * @return string file contents of the QR code
+     */
+    public function getQRCodeImage($qrtext, $size);
+
+    /**
+     * Returns the appropriate mime type for the QR code
+     * that will be generated
+     *
+     * @return string
+     */
+    public function getMimeType();
+}

+ 60 - 0
vendor/robthree/twofactorauth/lib/Providers/Qr/ImageChartsQRCodeProvider.php

@@ -0,0 +1,60 @@
+<?php
+
+namespace RobThree\Auth\Providers\Qr;
+
+// https://image-charts.com
+class ImageChartsQRCodeProvider extends BaseHTTPQRCodeProvider
+{
+    /** @var string */
+    public $errorcorrectionlevel;
+
+    /** @var int */
+    public $margin;
+
+    /**
+     * @param bool $verifyssl
+     * @param string $errorcorrectionlevel
+     * @param int $margin
+     */
+    public function __construct($verifyssl = false, $errorcorrectionlevel = 'L', $margin = 1)
+    {
+        if (!is_bool($verifyssl)) {
+            throw new QRException('VerifySSL must be bool');
+        }
+
+        $this->verifyssl = $verifyssl;
+
+        $this->errorcorrectionlevel = $errorcorrectionlevel;
+        $this->margin = $margin;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getMimeType()
+    {
+        return 'image/png';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getQRCodeImage($qrtext, $size)
+    {
+        return $this->getContent($this->getUrl($qrtext, $size));
+    }
+
+    /**
+     * @param string $qrtext the value to encode in the QR code
+     * @param int $size the desired size of the QR code
+     *
+     * @return string file contents of the QR code
+     */
+    public function getUrl($qrtext, $size)
+    {
+        return 'https://image-charts.com/chart?cht=qr'
+            . '&chs=' . ceil($size / 2) . 'x' . ceil($size / 2)
+            . '&chld=' . $this->errorcorrectionlevel . '|' . $this->margin
+            . '&chl=' . rawurlencode($qrtext);
+    }
+}

+ 7 - 0
vendor/robthree/twofactorauth/lib/Providers/Qr/QRException.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace RobThree\Auth\Providers\Qr;
+
+use RobThree\Auth\TwoFactorAuthException;
+
+class QRException extends TwoFactorAuthException {}

+ 108 - 0
vendor/robthree/twofactorauth/lib/Providers/Qr/QRServerProvider.php

@@ -0,0 +1,108 @@
+<?php
+
+namespace RobThree\Auth\Providers\Qr;
+
+// http://goqr.me/api/doc/create-qr-code/
+class QRServerProvider extends BaseHTTPQRCodeProvider
+{
+    /** @var string */
+    public $errorcorrectionlevel;
+
+    /** @var int */
+    public $margin;
+
+    /** @var int */
+    public $qzone;
+
+    /** @var string */
+    public $bgcolor;
+
+    /** @var string */
+    public $color;
+
+    /** @var string */
+    public $format;
+
+    /**
+     * @param bool $verifyssl
+     * @param string $errorcorrectionlevel
+     * @param int $margin
+     * @param int $qzone
+     * @param string $bgcolor
+     * @param string $color
+     * @param string $format
+     */
+    public function __construct($verifyssl = false, $errorcorrectionlevel = 'L', $margin = 4, $qzone = 1, $bgcolor = 'ffffff', $color = '000000', $format = 'png')
+    {
+        if (!is_bool($verifyssl)) {
+            throw new QRException('VerifySSL must be bool');
+        }
+
+        $this->verifyssl = $verifyssl;
+
+        $this->errorcorrectionlevel = $errorcorrectionlevel;
+        $this->margin = $margin;
+        $this->qzone = $qzone;
+        $this->bgcolor = $bgcolor;
+        $this->color = $color;
+        $this->format = $format;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getMimeType()
+    {
+        switch (strtolower($this->format)) {
+            case 'png':
+                return 'image/png';
+            case 'gif':
+                return 'image/gif';
+            case 'jpg':
+            case 'jpeg':
+                return 'image/jpeg';
+            case 'svg':
+                return 'image/svg+xml';
+            case 'eps':
+                return 'application/postscript';
+        }
+        throw new QRException(sprintf('Unknown MIME-type: %s', $this->format));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getQRCodeImage($qrtext, $size)
+    {
+        return $this->getContent($this->getUrl($qrtext, $size));
+    }
+
+    /**
+     * @param string $value
+     *
+     * @return string
+     */
+    private function decodeColor($value)
+    {
+        return vsprintf('%d-%d-%d', sscanf($value, "%02x%02x%02x"));
+    }
+
+    /**
+     * @param string $qrtext the value to encode in the QR code
+     * @param int|string $size the desired size of the QR code
+     *
+     * @return string file contents of the QR code
+     */
+    public function getUrl($qrtext, $size)
+    {
+        return 'https://api.qrserver.com/v1/create-qr-code/'
+            . '?size=' . $size . 'x' . $size
+            . '&ecc=' . strtoupper($this->errorcorrectionlevel)
+            . '&margin=' . $this->margin
+            . '&qzone=' . $this->qzone
+            . '&bgcolor=' . $this->decodeColor($this->bgcolor)
+            . '&color=' . $this->decodeColor($this->color)
+            . '&format=' . strtolower($this->format)
+            . '&data=' . rawurlencode($qrtext);
+    }
+}

+ 76 - 0
vendor/robthree/twofactorauth/lib/Providers/Qr/QRicketProvider.php

@@ -0,0 +1,76 @@
+<?php
+
+namespace RobThree\Auth\Providers\Qr;
+
+// http://qrickit.com/qrickit_apps/qrickit_api.php
+class QRicketProvider extends BaseHTTPQRCodeProvider
+{
+    /** @var string */
+    public $errorcorrectionlevel;
+
+    /** @var string */
+    public $bgcolor;
+
+    /** @var string */
+    public $color;
+
+    /** @var string */
+    public $format;
+
+    /**
+     * @param string $errorcorrectionlevel
+     * @param string $bgcolor
+     * @param string $color
+     * @param string $format
+     */
+    public function __construct($errorcorrectionlevel = 'L', $bgcolor = 'ffffff', $color = '000000', $format = 'p')
+    {
+        $this->verifyssl = false;
+
+        $this->errorcorrectionlevel = $errorcorrectionlevel;
+        $this->bgcolor = $bgcolor;
+        $this->color = $color;
+        $this->format = $format;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getMimeType()
+    {
+        switch (strtolower($this->format)) {
+            case 'p':
+                return 'image/png';
+            case 'g':
+                return 'image/gif';
+            case 'j':
+                return 'image/jpeg';
+        }
+        throw new QRException(sprintf('Unknown MIME-type: %s', $this->format));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getQRCodeImage($qrtext, $size)
+    {
+        return $this->getContent($this->getUrl($qrtext, $size));
+    }
+
+    /**
+     * @param string $qrtext the value to encode in the QR code
+     * @param int|string $size the desired size of the QR code
+     *
+     * @return string file contents of the QR code
+     */
+    public function getUrl($qrtext, $size)
+    {
+        return 'http://qrickit.com/api/qr'
+            . '?qrsize=' . $size
+            . '&e=' . strtolower($this->errorcorrectionlevel)
+            . '&bgdcolor=' . $this->bgcolor
+            . '&fgdcolor=' . $this->color
+            . '&t=' . strtolower($this->format)
+            . '&d=' . rawurlencode($qrtext);
+    }
+}

+ 22 - 0
vendor/robthree/twofactorauth/lib/Providers/Rng/CSRNGProvider.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace RobThree\Auth\Providers\Rng;
+
+class CSRNGProvider implements IRNGProvider
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function getRandomBytes($bytecount)
+    {
+        return random_bytes($bytecount);    // PHP7+
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function isCryptographicallySecure()
+    {
+        return true;
+    }
+}

+ 43 - 0
vendor/robthree/twofactorauth/lib/Providers/Rng/HashRNGProvider.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace RobThree\Auth\Providers\Rng;
+
+class HashRNGProvider implements IRNGProvider
+{
+    /** @var string */
+    private $algorithm;
+
+    /**
+     * @param string $algorithm
+     */
+    public function __construct($algorithm = 'sha256')
+    {
+        $algos = array_values(hash_algos());
+        if (!in_array($algorithm, $algos, true)) {
+            throw new RNGException('Unsupported algorithm specified');
+        }
+        $this->algorithm = $algorithm;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getRandomBytes($bytecount)
+    {
+        $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()
+    {
+        return false;
+    }
+}

+ 18 - 0
vendor/robthree/twofactorauth/lib/Providers/Rng/IRNGProvider.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace RobThree\Auth\Providers\Rng;
+
+interface IRNGProvider
+{
+    /**
+     * @param int $bytecount the number of bytes of randomness to return
+     *
+     * @return string the random bytes
+     */
+    public function getRandomBytes($bytecount);
+
+    /**
+     * @return bool whether this provider is cryptographically secure
+     */
+    public function isCryptographicallySecure();
+}

+ 37 - 0
vendor/robthree/twofactorauth/lib/Providers/Rng/MCryptRNGProvider.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace RobThree\Auth\Providers\Rng;
+
+class MCryptRNGProvider implements IRNGProvider
+{
+    /** @var int */
+    private $source;
+
+    /**
+     * @param int $source
+     */
+    public function __construct($source = MCRYPT_DEV_URANDOM)
+    {
+        $this->source = $source;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getRandomBytes($bytecount)
+    {
+        $result = @mcrypt_create_iv($bytecount, $this->source);
+        if ($result === false) {
+            throw new RNGException('mcrypt_create_iv returned an invalid value');
+        }
+        return $result;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function isCryptographicallySecure()
+    {
+        return true;
+    }
+}

+ 40 - 0
vendor/robthree/twofactorauth/lib/Providers/Rng/OpenSSLRNGProvider.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace RobThree\Auth\Providers\Rng;
+
+class OpenSSLRNGProvider implements IRNGProvider
+{
+    /** @var bool */
+    private $requirestrong;
+
+    /**
+     * @param bool $requirestrong
+     */
+    public function __construct($requirestrong = true)
+    {
+        $this->requirestrong = $requirestrong;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getRandomBytes($bytecount)
+    {
+        $result = openssl_random_pseudo_bytes($bytecount, $crypto_strong);
+        if ($this->requirestrong && ($crypto_strong === false)) {
+            throw new RNGException('openssl_random_pseudo_bytes returned non-cryptographically strong value');
+        }
+        if ($result === false) {
+            throw new RNGException('openssl_random_pseudo_bytes returned an invalid value');
+        }
+        return $result;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function isCryptographicallySecure()
+    {
+        return $this->requirestrong;
+    }
+}

+ 7 - 0
vendor/robthree/twofactorauth/lib/Providers/Rng/RNGException.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace RobThree\Auth\Providers\Rng;
+
+use RobThree\Auth\TwoFactorAuthException;
+
+class RNGException extends TwoFactorAuthException {}

+ 71 - 0
vendor/robthree/twofactorauth/lib/Providers/Time/HttpTimeProvider.php

@@ -0,0 +1,71 @@
+<?php
+
+namespace RobThree\Auth\Providers\Time;
+
+use DateTime;
+
+/**
+ * Takes the time from any webserver by doing a HEAD request on the specified URL and extracting the 'Date:' header
+ */
+class HttpTimeProvider implements ITimeProvider
+{
+    /** @var string */
+    public $url;
+
+    /** @var string */
+    public $expectedtimeformat;
+
+    /** @var array */
+    public $options;
+
+    /**
+     * @param string $url
+     * @param string $expectedtimeformat
+     * @param array $options
+     */
+    public function __construct($url = 'https://google.com', $expectedtimeformat = 'D, d M Y H:i:s O+', array $options = null)
+    {
+        $this->url = $url;
+        $this->expectedtimeformat = $expectedtimeformat;
+        if ($options === null) {
+            $options = array(
+                'http' => array(
+                    'method' => 'HEAD',
+                    'follow_location' => false,
+                    'ignore_errors' => true,
+                    'max_redirects' => 0,
+                    'request_fulluri' => true,
+                    'header' => array(
+                        'Connection: close',
+                        'User-agent: TwoFactorAuth HttpTimeProvider (https://github.com/RobThree/TwoFactorAuth)',
+                        'Cache-Control: no-cache'
+                    )
+                )
+            );
+        }
+        $this->options = $options;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getTime()
+    {
+        try {
+            $context  = stream_context_create($this->options);
+            $fd = fopen($this->url, 'rb', false, $context);
+            $headers = stream_get_meta_data($fd);
+            fclose($fd);
+
+            foreach ($headers['wrapper_data'] as $h) {
+                if (strcasecmp(substr($h, 0, 5), 'Date:') === 0) {
+                    return DateTime::createFromFormat($this->expectedtimeformat, trim(substr($h, 5)))->getTimestamp();
+                }
+            }
+            throw new \Exception('Invalid or no "Date:" header found');
+        } catch (\Exception $ex) {
+            throw new TimeException(sprintf('Unable to retrieve time from %s (%s)', $this->url, $ex->getMessage()));
+        }
+
+    }
+}

+ 11 - 0
vendor/robthree/twofactorauth/lib/Providers/Time/ITimeProvider.php

@@ -0,0 +1,11 @@
+<?php
+
+namespace RobThree\Auth\Providers\Time;
+
+interface ITimeProvider
+{
+    /**
+     * @return int the current timestamp according to this provider
+     */
+    public function getTime();
+}

+ 11 - 0
vendor/robthree/twofactorauth/lib/Providers/Time/LocalMachineTimeProvider.php

@@ -0,0 +1,11 @@
+<?php
+
+namespace RobThree\Auth\Providers\Time;
+
+class LocalMachineTimeProvider implements ITimeProvider
+{
+    public function getTime()
+    {
+        return time();
+    }
+}

+ 70 - 0
vendor/robthree/twofactorauth/lib/Providers/Time/NTPTimeProvider.php

@@ -0,0 +1,70 @@
+<?php
+
+namespace RobThree\Auth\Providers\Time;
+
+/**
+ * Takes the time from any NTP server
+ */
+class NTPTimeProvider implements ITimeProvider
+{
+    /** @var string */
+    public $host;
+
+    /** @var int */
+    public $port;
+
+    /** @var int */
+    public $timeout;
+
+    /**
+     * @param string $host
+     * @param int $port
+     * @param int $timeout
+     */
+    public function __construct($host = 'time.google.com', $port = 123, $timeout = 1)
+    {
+        $this->host = $host;
+
+        if (!is_int($port) || $port <= 0 || $port > 65535) {
+            throw new TimeException('Port must be 0 < port < 65535');
+        }
+        $this->port = $port;
+
+        if (!is_int($timeout) || $timeout < 0) {
+            throw new TimeException('Timeout must be >= 0');
+        }
+        $this->timeout = $timeout;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getTime()
+    {
+        try {
+            /* Create a socket and connect to NTP server */
+            $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
+            socket_set_option($sock, SOL_SOCKET, SO_RCVTIMEO, ['sec' => $this->timeout, 'usec' => 0]);
+            socket_connect($sock, $this->host, $this->port);
+
+            /* Send request */
+            $msg = "\010" . str_repeat("\0", 47);
+            socket_send($sock, $msg, strlen($msg), 0);
+
+            /* Receive response and close socket */
+            if (socket_recv($sock, $recv, 48, MSG_WAITALL) === false) {
+                throw new \Exception(socket_strerror(socket_last_error($sock)));
+            }
+            socket_close($sock);
+
+            /* Interpret response */
+            $data = unpack('N12', $recv);
+            $timestamp = (int) sprintf('%u', $data[9]);
+
+            /* NTP is number of seconds since 0000 UT on 1 January 1900 Unix time is seconds since 0000 UT on 1 January 1970 */
+            return $timestamp - 2208988800;
+        } catch (\Exception $ex) {
+            throw new TimeException(sprintf('Unable to retrieve time from %s (%s)', $this->host, $ex->getMessage()));
+        }
+    }
+}

+ 7 - 0
vendor/robthree/twofactorauth/lib/Providers/Time/TimeException.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace RobThree\Auth\Providers\Time;
+
+use RobThree\Auth\TwoFactorAuthException;
+
+class TimeException extends TwoFactorAuthException {}

+ 360 - 0
vendor/robthree/twofactorauth/lib/TwoFactorAuth.php

@@ -0,0 +1,360 @@
+<?php
+
+namespace RobThree\Auth;
+
+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\MCryptRNGProvider;
+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;
+
+// Based on / inspired by: https://github.com/PHPGangsta/GoogleAuthenticator
+// Algorithms, digits, period etc. explained: https://github.com/google/google-authenticator/wiki/Key-Uri-Format
+class TwoFactorAuth
+{
+    /** @var string */
+    private $algorithm;
+
+    /** @var int */
+    private $period;
+
+    /** @var int */
+    private $digits;
+
+    /** @var string */
+    private $issuer;
+
+    /** @var ?IQRCodeProvider */
+    private $qrcodeprovider = null;
+
+    /** @var ?IRNGProvider */
+    private $rngprovider = null;
+
+    /** @var ?ITimeProvider */
+    private $timeprovider = null;
+
+    /** @var string */
+    private static $_base32dict = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=';
+
+    /** @var array */
+    private static $_base32;
+
+    /** @var array */
+    private static $_base32lookup = array();
+
+    /** @var array */
+    private static $_supportedalgos = array('sha1', 'sha256', 'sha512', 'md5');
+
+    /**
+     * @param ?string $issuer
+     * @param int $digits
+     * @param int $period
+     * @param string $algorithm
+     * @param ?IQRCodeProvider $qrcodeprovider
+     * @param ?IRNGProvider $rngprovider
+     * @param ?ITimeProvider $timeprovider
+     */
+    public function __construct($issuer = null, $digits = 6, $period = 30, $algorithm = 'sha1', IQRCodeProvider $qrcodeprovider = null, IRNGProvider $rngprovider = null, ITimeProvider $timeprovider = null)
+    {
+        $this->issuer = $issuer;
+        if (!is_int($digits) || $digits <= 0) {
+            throw new TwoFactorAuthException('Digits must be int > 0');
+        }
+        $this->digits = $digits;
+
+        if (!is_int($period) || $period <= 0) {
+            throw new TwoFactorAuthException('Period must be int > 0');
+        }
+        $this->period = $period;
+
+        $algorithm = strtolower(trim($algorithm));
+        if (!in_array($algorithm, self::$_supportedalgos)) {
+            throw new TwoFactorAuthException('Unsupported algorithm: ' . $algorithm);
+        }
+        $this->algorithm = $algorithm;
+        $this->qrcodeprovider = $qrcodeprovider;
+        $this->rngprovider = $rngprovider;
+        $this->timeprovider = $timeprovider;
+
+        self::$_base32 = str_split(self::$_base32dict);
+        self::$_base32lookup = array_flip(self::$_base32);
+    }
+
+    /**
+     * Create a new secret
+     *
+     * @param int $bits
+     * @param bool $requirecryptosecure
+     *
+     * @return string
+     */
+    public function createSecret($bits = 80, $requirecryptosecure = true)
+    {
+        $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
+        }
+        return $secret;
+    }
+
+    /**
+     * Calculate the code with given secret and point in time
+     *
+     * @param string $secret
+     * @param ?int $time
+     *
+     * @return string
+     */
+    public function getCode($secret, $time = null)
+    {
+        $secretkey = $this->base32Decode($secret);
+
+        $timestamp = "\0\0\0\0" . pack('N*', $this->getTimeSlice($this->getTime($time)));  // Pack time into binary string
+        $hashhmac = hash_hmac($this->algorithm, $timestamp, $secretkey, true);             // Hash it with users secret key
+        $hashpart = substr($hashhmac, ord(substr($hashhmac, -1)) & 0x0F, 4);               // Use last nibble of result as index/offset and grab 4 bytes of the result
+        $value = unpack('N', $hashpart);                                                   // Unpack binary value
+        $value = $value[1] & 0x7FFFFFFF;                                                   // Drop MSB, keep only 31 bits
+
+        return str_pad((string) ($value % pow(10, $this->digits)), $this->digits, '0', STR_PAD_LEFT);
+    }
+
+    /**
+     * Check if the code is correct. This will accept codes starting from ($discrepancy * $period) sec ago to ($discrepancy * period) sec from now
+     *
+     * @param string $secret
+     * @param string $code
+     * @param int $discrepancy
+     * @param ?int $time
+     * @param int $timeslice
+     *
+     * @return bool
+     */
+    public function verifyCode($secret, $code, $discrepancy = 1, $time = null, &$timeslice = 0)
+    {
+        $timestamp = $this->getTime($time);
+
+        $timeslice = 0;
+
+        // To keep safe from timing-attacks we iterate *all* possible codes even though we already may have
+        // verified a code is correct. We use the timeslice variable to hold either 0 (no match) or the timeslice
+        // of the match. Each iteration we either set the timeslice variable to the timeslice of the match
+        // or set the value to itself.  This is an effort to maintain constant execution time for the code.
+        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;
+        }
+
+        return $timeslice > 0;
+    }
+
+    /**
+     * Timing-attack safe comparison of 2 codes (see http://blog.ircmaxell.com/2014/11/its-all-about-time.html)
+     *
+     * @param string $safe
+     * @param string $user
+     *
+     * @return bool
+     */
+    private function codeEquals($safe, $user)
+    {
+        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;
+            for ($i = 0; $i < strlen($safe); $i++) {
+                $result |= (ord($safe[$i]) ^ ord($user[$i]));
+            }
+            return $result === 0;
+        }
+        return false;
+    }
+
+    /**
+     * Get data-uri of QRCode
+     *
+     * @param string $label
+     * @param string $secret
+     * @param mixed $size
+     *
+     * @return string
+     */
+    public function getQRCodeImageAsDataUri($label, $secret, $size = 200)
+    {
+        if (!is_int($size) || $size <= 0) {
+            throw new TwoFactorAuthException('Size must be int > 0');
+        }
+
+        $qrcodeprovider = $this->getQrCodeProvider();
+        return 'data:'
+            . $qrcodeprovider->getMimeType()
+            . ';base64,'
+            . base64_encode($qrcodeprovider->getQRCodeImage($this->getQRText($label, $secret), $size));
+    }
+
+    /**
+     * Compare default timeprovider with specified timeproviders and ensure the time is within the specified number of seconds (leniency)
+     * @param ?array $timeproviders
+     * @param int $leniency
+     *
+     * @return void
+     */
+    public function ensureCorrectTime(array $timeproviders = null, $leniency = 5)
+    {
+        if ($timeproviders === null) {
+            $timeproviders = array(
+                new NTPTimeProvider(),
+                new HttpTimeProvider()
+            );
+        }
+
+        // Get default time provider
+        $timeprovider = $this->getTimeProvider();
+
+        // Iterate specified time providers
+        foreach ($timeproviders as $t) {
+            if (!($t instanceof ITimeProvider)) {
+                throw new TwoFactorAuthException('Object does not implement ITimeProvider');
+            }
+
+            // Get time from default time provider and compare to specific time provider and throw if time difference is more than specified number of seconds leniency
+            if (abs($timeprovider->getTime() - $t->getTime()) > $leniency) {
+                throw new TwoFactorAuthException(sprintf('Time for timeprovider is off by more than %d seconds when compared to %s', $leniency, get_class($t)));
+            }
+        }
+    }
+
+    /**
+     * @param ?int $time
+     *
+     * @return int
+     */
+    private function getTime($time = null)
+    {
+        return ($time === null) ? $this->getTimeProvider()->getTime() : $time;
+    }
+
+    /**
+     * @param int $time
+     * @param int $offset
+     *
+     * @return int
+     */
+    private function getTimeSlice($time = null, $offset = 0)
+    {
+        return (int)floor($time / $this->period) + ($offset * $this->period);
+    }
+
+    /**
+     * Builds a string to be encoded in a QR code
+     *
+     * @param string $label
+     * @param string $secret
+     *
+     * @return string
+     */
+    public function getQRText($label, $secret)
+    {
+        return 'otpauth://totp/' . rawurlencode($label)
+            . '?secret=' . rawurlencode($secret)
+            . '&issuer=' . rawurlencode($this->issuer)
+            . '&period=' . intval($this->period)
+            . '&algorithm=' . rawurlencode(strtoupper($this->algorithm))
+            . '&digits=' . intval($this->digits);
+    }
+
+    /**
+     * @param string $value
+     * @return string
+     */
+    private function base32Decode($value)
+    {
+        if (strlen($value) == 0) {
+            return '';
+        }
+
+        if (preg_match('/[^' . preg_quote(self::$_base32dict) . ']/', $value) !== 0) {
+            throw new TwoFactorAuthException('Invalid base32 string');
+        }
+
+        $buffer = '';
+        foreach (str_split($value) as $char) {
+            if ($char !== '=') {
+                $buffer .= str_pad(decbin(self::$_base32lookup[$char]), 5, '0', STR_PAD_LEFT);
+            }
+        }
+        $length = strlen($buffer);
+        $blocks = trim(chunk_split(substr($buffer, 0, $length - ($length % 8)), 8, ' '));
+
+        $output = '';
+        foreach (explode(' ', $blocks) as $block) {
+            $output .= chr(bindec(str_pad($block, 8, '0', STR_PAD_RIGHT)));
+        }
+        return $output;
+    }
+
+    /**
+     * @return IQRCodeProvider
+     * @throws TwoFactorAuthException
+     */
+    public function getQrCodeProvider()
+    {
+        // Set default QR Code provider if none was specified
+        if (null === $this->qrcodeprovider) {
+            return $this->qrcodeprovider = new QRServerProvider();
+        }
+        return $this->qrcodeprovider;
+    }
+
+    /**
+     * @return IRNGProvider
+     * @throws TwoFactorAuthException
+     */
+    public function getRngProvider()
+    {
+        if (null !== $this->rngprovider) {
+            return $this->rngprovider;
+        }
+        if (function_exists('random_bytes')) {
+            return $this->rngprovider = new CSRNGProvider();
+        }
+        if (function_exists('mcrypt_create_iv')) {
+            return $this->rngprovider = new MCryptRNGProvider();
+        }
+        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 ITimeProvider
+     * @throws TwoFactorAuthException
+     */
+    public function getTimeProvider()
+    {
+        // Set default time provider if none was specified
+        if (null === $this->timeprovider) {
+            return $this->timeprovider = new LocalMachineTimeProvider();
+        }
+        return $this->timeprovider;
+    }
+}

+ 7 - 0
vendor/robthree/twofactorauth/lib/TwoFactorAuthException.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace RobThree\Auth;
+
+use Exception;
+
+class TwoFactorAuthException extends Exception {}

binární
vendor/robthree/twofactorauth/logo.png


binární
vendor/robthree/twofactorauth/multifactorauthforeveryone.png


+ 26 - 0
vendor/robthree/twofactorauth/phpunit.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit backupGlobals="false"
+         backupStaticAttributes="false"
+         colors="true"
+         convertErrorsToExceptions="true"
+         convertNoticesToExceptions="true"
+         convertWarningsToExceptions="true"
+         processIsolation="false"
+         xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         stopOnFailure="false">
+  <testsuites>
+    <testsuite name="Unit">
+      <directory suffix="Test.php">./tests</directory>
+    </testsuite>
+  </testsuites>
+  <coverage processUncoveredFiles="true">
+    <include>
+      <directory suffix=".php">./lib</directory>
+    </include>
+    <report>
+      <html outputDirectory="build/coverage"/>
+      <text outputFile="php://stdout"/>
+    </report>
+  </coverage>
+</phpunit>

+ 26 - 0
vendor/robthree/twofactorauth/tests/MightNotMakeAssertions.php

@@ -0,0 +1,26 @@
+<?php
+
+namespace Tests;
+
+trait MightNotMakeAssertions
+{
+    /**
+     * This is a shim to support PHPUnit for php 5.6 and 7.0.
+     *
+     * It has to be named something that doesn't collide with existing
+     * TestCase methods as we can't support PHP return types right now
+     *
+     * @return void
+     */
+    public function noAssertionsMade()
+    {
+        foreach (class_parents($this) as $parent) {
+            if (method_exists($parent, 'expectNotToPerformAssertions')) {
+                parent::expectNotToPerformAssertions();
+                return;
+            }
+        }
+
+        $this->assertTrue(true);
+    }
+}

+ 56 - 0
vendor/robthree/twofactorauth/tests/Providers/Qr/IQRCodeProviderTest.php

@@ -0,0 +1,56 @@
+<?php
+
+namespace Tests\Providers\Qr;
+
+use PHPUnit\Framework\TestCase;
+use RobThree\Auth\TwoFactorAuth;
+use RobThree\Auth\TwoFactorAuthException;
+
+class IQRCodeProviderTest extends TestCase
+{
+    /**
+     * @param string $datauri
+     *
+     * @return null|array
+     */
+    private function DecodeDataUri($datauri)
+    {
+        if (preg_match('/data:(?P<mimetype>[\w\.\-\/]+);(?P<encoding>\w+),(?P<data>.*)/', $datauri, $m) === 1) {
+            return array(
+                'mimetype' => $m['mimetype'],
+                'encoding' => $m['encoding'],
+                'data' => base64_decode($m['data'])
+            );
+        }
+
+        return null;
+    }
+
+    /**
+     * @return void
+     */
+    public function testTotpUriIsCorrect()
+    {
+        $qr = new TestQrProvider();
+
+        $tfa = new TwoFactorAuth('Test&Issuer', 6, 30, 'sha1', $qr);
+        $data = $this->DecodeDataUri($tfa->getQRCodeImageAsDataUri('Test&Label', 'VMR466AB62ZBOKHE'));
+        $this->assertEquals('test/test', $data['mimetype']);
+        $this->assertEquals('base64', $data['encoding']);
+        $this->assertEquals('otpauth://totp/Test%26Label?secret=VMR466AB62ZBOKHE&issuer=Test%26Issuer&period=30&algorithm=SHA1&digits=6@200', $data['data']);
+    }
+
+    /**
+     * @return void
+     */
+    public function testGetQRCodeImageAsDataUriThrowsOnInvalidSize()
+    {
+        $qr = new TestQrProvider();
+
+        $tfa = new TwoFactorAuth('Test', 6, 30, 'sha1', $qr);
+
+        $this->expectException(TwoFactorAuthException::class);
+
+        $tfa->getQRCodeImageAsDataUri('Test', 'VMR466AB62ZBOKHE', 0);
+    }
+}

+ 24 - 0
vendor/robthree/twofactorauth/tests/Providers/Qr/TestQrProvider.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace Tests\Providers\Qr;
+
+use RobThree\Auth\Providers\Qr\IQRCodeProvider;
+
+class TestQrProvider implements IQRCodeProvider
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function getQRCodeImage($qrtext, $size)
+    {
+        return $qrtext . '@' . $size;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getMimeType()
+    {
+        return 'test/test';
+    }
+}

+ 30 - 0
vendor/robthree/twofactorauth/tests/Providers/Rng/CSRNGProviderTest.php

@@ -0,0 +1,30 @@
+<?php
+
+namespace Tests\Providers\Rng;
+
+use PHPUnit\Framework\TestCase;
+use Tests\MightNotMakeAssertions;
+use RobThree\Auth\Providers\Rng\CSRNGProvider;
+
+class CSRNGProviderTest extends TestCase
+{
+    use NeedsRngLengths, MightNotMakeAssertions;
+
+    /**
+     * @requires function random_bytes
+     *
+     * @return void
+     */
+    public function testCSRNGProvidersReturnExpectedNumberOfBytes()
+    {
+        if (function_exists('random_bytes')) {
+            $rng = new CSRNGProvider();
+            foreach ($this->rngTestLengths as $l) {
+                $this->assertEquals($l, strlen($rng->getRandomBytes($l)));
+            }
+            $this->assertTrue($rng->isCryptographicallySecure());
+        } else {
+            $this->noAssertionsMade();
+        }
+    }
+}

+ 24 - 0
vendor/robthree/twofactorauth/tests/Providers/Rng/HashRNGProviderTest.php

@@ -0,0 +1,24 @@
+<?php
+
+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->assertEquals($l, strlen($rng->getRandomBytes($l)));
+        }
+
+        $this->assertFalse($rng->isCryptographicallySecure());
+    }
+}

+ 61 - 0
vendor/robthree/twofactorauth/tests/Providers/Rng/IRNGProviderTest.php

@@ -0,0 +1,61 @@
+<?php
+
+namespace Tests\Providers\Rng;
+
+use PHPUnit\Framework\TestCase;
+use RobThree\Auth\TwoFactorAuth;
+use RobThree\Auth\TwoFactorAuthException;
+
+class IRNGProviderTest extends TestCase
+{
+    /**
+     * @return void
+     */
+    public function testCreateSecretThrowsOnInsecureRNGProvider()
+    {
+        $rng = new TestRNGProvider();
+
+        $tfa = new TwoFactorAuth('Test', 6, 30, 'sha1', null, $rng);
+
+        $this->expectException(TwoFactorAuthException::class);
+        $tfa->createSecret();
+    }
+
+    /**
+     * @return void
+     */
+    public function testCreateSecretOverrideSecureDoesNotThrowOnInsecureRNG()
+    {
+        $rng = new TestRNGProvider();
+
+        $tfa = new TwoFactorAuth('Test', 6, 30, 'sha1', null, $rng);
+        $this->assertEquals('ABCDEFGHIJKLMNOP', $tfa->createSecret(80, false));
+    }
+
+    /**
+     * @return void
+     */
+    public function testCreateSecretDoesNotThrowOnSecureRNGProvider()
+    {
+        $rng = new TestRNGProvider(true);
+
+        $tfa = new TwoFactorAuth('Test', 6, 30, 'sha1', null, $rng);
+        $this->assertEquals('ABCDEFGHIJKLMNOP', $tfa->createSecret());
+    }
+
+    /**
+     * @return void
+     */
+    public function testCreateSecretGeneratesDesiredAmountOfEntropy()
+    {
+        $rng = new TestRNGProvider(true);
+
+        $tfa = new TwoFactorAuth('Test', 6, 30, 'sha1', null, $rng);
+        $this->assertEquals('A', $tfa->createSecret(5));
+        $this->assertEquals('AB', $tfa->createSecret(6));
+        $this->assertEquals('ABCDEFGHIJKLMNOPQRSTUVWXYZ', $tfa->createSecret(128));
+        $this->assertEquals('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', $tfa->createSecret(160));
+        $this->assertEquals('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', $tfa->createSecret(320));
+        $this->assertEquals('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567ABCDEFGHIJKLMNOPQRSTUVWXYZ234567A', $tfa->createSecret(321));
+    }
+}

+ 32 - 0
vendor/robthree/twofactorauth/tests/Providers/Rng/MCryptRNGProviderTest.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace Tests\Providers\Rng;
+
+use PHPUnit\Framework\TestCase;
+use Tests\MightNotMakeAssertions;
+use RobThree\Auth\Providers\Rng\MCryptRNGProvider;
+
+class MCryptRNGProviderTest extends TestCase
+{
+    use NeedsRngLengths, MightNotMakeAssertions;
+
+    /**
+     * @requires function mcrypt_create_iv
+     *
+     * @return void
+     */
+    public function testMCryptRNGProvidersReturnExpectedNumberOfBytes()
+    {
+        if (function_exists('mcrypt_create_iv')) {
+            $rng = new MCryptRNGProvider();
+
+            foreach ($this->rngTestLengths as $l) {
+                $this->assertEquals($l, strlen($rng->getRandomBytes($l)));
+            }
+
+            $this->assertTrue($rng->isCryptographicallySecure());
+        } else {
+            $this->noAssertionsMade();
+        }
+    }
+}

+ 9 - 0
vendor/robthree/twofactorauth/tests/Providers/Rng/NeedsRngLengths.php

@@ -0,0 +1,9 @@
+<?php
+
+namespace Tests\Providers\Rng;
+
+trait NeedsRngLengths
+{
+    /** @var array */
+    protected $rngTestLengths = array(1, 16, 32, 256);
+}

+ 37 - 0
vendor/robthree/twofactorauth/tests/Providers/Rng/OpenSSLRNGProviderTest.php

@@ -0,0 +1,37 @@
+<?php
+
+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->assertEquals($l, strlen($rng->getRandomBytes($l)));
+        }
+
+        $this->assertTrue($rng->isCryptographicallySecure());
+    }
+
+    /**
+     * @return void
+     */
+    public function testNonStrongOpenSSLRNGProvidersReturnExpectedNumberOfBytes()
+    {
+        $rng = new OpenSSLRNGProvider(false);
+        foreach ($this->rngTestLengths as $l) {
+            $this->assertEquals($l, strlen($rng->getRandomBytes($l)));
+        }
+
+        $this->assertFalse($rng->isCryptographicallySecure());
+    }
+}

+ 41 - 0
vendor/robthree/twofactorauth/tests/Providers/Rng/TestRNGProvider.php

@@ -0,0 +1,41 @@
+<?php
+
+namespace Tests\Providers\Rng;
+
+use RobThree\Auth\Providers\Rng\IRNGProvider;
+
+class TestRNGProvider implements IRNGProvider
+{
+    /** @var bool */
+    private $isSecure;
+
+    /**
+     * @param bool $isSecure whether this provider is cryptographically secure
+     */
+    function __construct($isSecure = false)
+    {
+        $this->isSecure = $isSecure;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getRandomBytes($bytecount)
+    {
+        $result = '';
+
+        for ($i = 0; $i < $bytecount; $i++) {
+            $result .= chr($i);
+        }
+
+        return $result;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function isCryptographicallySecure()
+    {
+        return $this->isSecure;
+    }
+}

+ 53 - 0
vendor/robthree/twofactorauth/tests/Providers/Time/ITimeProviderTest.php

@@ -0,0 +1,53 @@
+<?php
+
+namespace Tests\Providers\Time;
+
+use PHPUnit\Framework\TestCase;
+use Tests\MightNotMakeAssertions;
+use RobThree\Auth\TwoFactorAuthException;
+use RobThree\Auth\TwoFactorAuth;
+
+class ITimeProviderTest extends TestCase
+{
+    use MightNotMakeAssertions;
+
+    /**
+     * @return void
+     */
+    public function testEnsureCorrectTimeDoesNotThrowForCorrectTime()
+    {
+        $tpr1 = new TestTimeProvider(123);
+        $tpr2 = new TestTimeProvider(128);
+
+        $tfa = new TwoFactorAuth('Test', 6, 30, 'sha1', null, null, $tpr1);
+        $tfa->ensureCorrectTime(array($tpr2));   // 128 - 123 = 5 => within default leniency
+
+        $this->noAssertionsMade();
+    }
+
+    /**
+     * @return void
+     */
+    public function testEnsureCorrectTimeThrowsOnIncorrectTime()
+    {
+        $tpr1 = new TestTimeProvider(123);
+        $tpr2 = new TestTimeProvider(124);
+
+        $tfa = new TwoFactorAuth('Test', 6, 30, 'sha1', null, null, $tpr1);
+
+        $this->expectException(TwoFactorAuthException::class);
+
+        $tfa->ensureCorrectTime(array($tpr2), 0);    // We force a leniency of 0, 124-123 = 1 so this should throw
+    }
+
+    /**
+     * @return void
+     */
+    public function testEnsureDefaultTimeProviderReturnsCorrectTime()
+    {
+        $tfa = new TwoFactorAuth('Test', 6, 30, 'sha1');
+        $tfa->ensureCorrectTime(array(new TestTimeProvider(time())), 1);    // Use a leniency of 1, should the time change between both time() calls
+
+        $this->noAssertionsMade();
+    }
+}

+ 27 - 0
vendor/robthree/twofactorauth/tests/Providers/Time/TestTimeProvider.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace Tests\Providers\Time;
+
+use RobThree\Auth\Providers\Time\ITimeProvider;
+
+class TestTimeProvider implements ITimeProvider
+{
+    /** @var int */
+    private $time;
+
+    /**
+     * @param int $time
+     */
+    function __construct($time)
+    {
+        $this->time = $time;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getTime()
+    {
+        return $this->time;
+    }
+}

+ 246 - 0
vendor/robthree/twofactorauth/tests/TwoFactorAuthTest.php

@@ -0,0 +1,246 @@
+<?php
+
+namespace Tests;
+
+use PHPUnit\Framework\TestCase;
+use RobThree\Auth\TwoFactorAuthException;
+use RobThree\Auth\TwoFactorAuth;
+
+class TwoFactorAuthTest extends TestCase
+{
+    use MightNotMakeAssertions;
+
+    /**
+     * @return void
+     */
+    public function testConstructorThrowsOnInvalidDigits()
+    {
+        $this->expectException(TwoFactorAuthException::class);
+
+        new TwoFactorAuth('Test', 0);
+    }
+
+    /**
+     * @return void
+     */
+    public function testConstructorThrowsOnInvalidPeriod()
+    {
+        $this->expectException(TwoFactorAuthException::class);
+
+        new TwoFactorAuth('Test', 6, 0);
+    }
+
+    /**
+     * @return void
+     */
+    public function testConstructorThrowsOnInvalidAlgorithm()
+    {
+        $this->expectException(TwoFactorAuthException::class);
+
+        new TwoFactorAuth('Test', 6, 30, 'xxx');
+    }
+
+    /**
+     * @return void
+     */
+    public function testGetCodeReturnsCorrectResults()
+    {
+        $tfa = new TwoFactorAuth('Test');
+        $this->assertEquals('543160', $tfa->getCode('VMR466AB62ZBOKHE', 1426847216));
+        $this->assertEquals('538532', $tfa->getCode('VMR466AB62ZBOKHE', 0));
+    }
+
+    /**
+     * @return void
+     */
+    public function testEnsureAllTimeProvidersReturnCorrectTime()
+    {
+        $tfa = new TwoFactorAuth('Test', 6, 30, 'sha1');
+        $tfa->ensureCorrectTime(array(
+            new \RobThree\Auth\Providers\Time\NTPTimeProvider(),                         // Uses pool.ntp.org by default
+            //new \RobThree\Auth\Providers\Time\NTPTimeProvider('time.google.com'),      // Somehow time.google.com and time.windows.com make travis timeout??
+            new \RobThree\Auth\Providers\Time\HttpTimeProvider(),                        // Uses google.com by default
+            new \RobThree\Auth\Providers\Time\HttpTimeProvider('https://github.com'),
+            new \RobThree\Auth\Providers\Time\HttpTimeProvider('https://yahoo.com'),
+        ));
+        $this->noAssertionsMade();
+    }
+
+    /**
+     * @return void
+     */
+    public function testVerifyCodeWorksCorrectly()
+    {
+        $tfa = new TwoFactorAuth('Test', 6, 30);
+        $this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847190));
+        $this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 0, 1426847190 + 29));	//Test discrepancy
+        $this->assertFalse($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 0, 1426847190 + 30));	//Test discrepancy
+        $this->assertFalse($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 0, 1426847190 - 1));	//Test discrepancy
+
+        $this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205 + 0));	//Test discrepancy
+        $this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205 + 35));	//Test discrepancy
+        $this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205 - 35));	//Test discrepancy
+
+        $this->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
+    }
+
+    /**
+     * @return void
+     */
+    public function testVerifyCorrectTimeSliceIsReturned()
+    {
+        $tfa = new TwoFactorAuth('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
+        $this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '534113', 3, 1426847190, $timeslice1));
+        $this->assertEquals(47561570, $timeslice1);
+        $this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '819652', 3, 1426847190, $timeslice2));
+        $this->assertEquals(47561571, $timeslice2);
+        $this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '915954', 3, 1426847190, $timeslice3));
+        $this->assertEquals(47561572, $timeslice3);
+        $this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 3, 1426847190, $timeslice4));
+        $this->assertEquals(47561573, $timeslice4);
+        $this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '348401', 3, 1426847190, $timeslice5));
+        $this->assertEquals(47561574, $timeslice5);
+        $this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '648525', 3, 1426847190, $timeslice6));
+        $this->assertEquals(47561575, $timeslice6);
+        $this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '170645', 3, 1426847190, $timeslice7));
+        $this->assertEquals(47561576, $timeslice7);
+
+        // Incorrect code should return false and a 0 timeslice
+        $this->assertFalse($tfa->verifyCode('VMR466AB62ZBOKHE', '111111', 3, 1426847190, $timeslice8));
+        $this->assertEquals(0, $timeslice8);
+    }
+
+    /**
+     * @return void
+     */
+    public function testGetCodeThrowsOnInvalidBase32String1()
+    {
+        $tfa = new TwoFactorAuth('Test');
+
+        $this->expectException(TwoFactorAuthException::class);
+
+        $tfa->getCode('FOO1BAR8BAZ9');    //1, 8 & 9 are invalid chars
+    }
+
+    /**
+     * @return void
+     */
+    public function testGetCodeThrowsOnInvalidBase32String2()
+    {
+        $tfa = new TwoFactorAuth('Test');
+
+        $this->expectException(TwoFactorAuthException::class);
+
+        $tfa->getCode('mzxw6===');        //Lowercase
+    }
+
+    /**
+     * @return void
+     */
+    public function testKnownBase32DecodeTestVectors()
+    {
+        // We usually don't test internals (e.g. privates) but since we rely heavily on base32 decoding and don't want
+        // to expose this method nor do we want to give people the possibility of implementing / providing their own base32
+        // decoding/decoder (as we do with Rng/QR providers for example) we simply test the private base32Decode() method
+        // with some known testvectors **only** to ensure base32 decoding works correctly following RFC's so there won't
+        // be any bugs hiding in there. We **could** 'fool' ourselves by calling the public getCode() method (which uses
+        // base32decode internally) and then make sure getCode's output (in digits) equals expected output since that would
+        // mean the base32Decode() works as expected but that **could** hide some subtle bug(s) in decoding the base32 string.
+
+        // "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');
+
+        $method = new \ReflectionMethod(TwoFactorAuth::class, 'base32Decode');
+        $method->setAccessible(true);
+
+        // Test vectors from: https://tools.ietf.org/html/rfc4648#page-12
+        $this->assertEquals('', $method->invoke($tfa, ''));
+        $this->assertEquals('f', $method->invoke($tfa, 'MY======'));
+        $this->assertEquals('fo', $method->invoke($tfa, 'MZXQ===='));
+        $this->assertEquals('foo', $method->invoke($tfa, 'MZXW6==='));
+        $this->assertEquals('foob', $method->invoke($tfa, 'MZXW6YQ='));
+        $this->assertEquals('fooba', $method->invoke($tfa, 'MZXW6YTB'));
+        $this->assertEquals('foobar', $method->invoke($tfa, 'MZXW6YTBOI======'));
+    }
+
+    /**
+     * @return void
+     */
+    public function testKnownBase32DecodeUnpaddedTestVectors()
+    {
+        // See testKnownBase32DecodeTestVectors() for the rationale behind testing the private base32Decode() method.
+        // 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');
+
+        $method = new \ReflectionMethod(TwoFactorAuth::class, 'base32Decode');
+        $method->setAccessible(true);
+
+        // Test vectors from: https://tools.ietf.org/html/rfc4648#page-12
+        $this->assertEquals('', $method->invoke($tfa, ''));
+        $this->assertEquals('f', $method->invoke($tfa, 'MY'));
+        $this->assertEquals('fo', $method->invoke($tfa, 'MZXQ'));
+        $this->assertEquals('foo', $method->invoke($tfa, 'MZXW6'));
+        $this->assertEquals('foob', $method->invoke($tfa, 'MZXW6YQ'));
+        $this->assertEquals('fooba', $method->invoke($tfa, 'MZXW6YTB'));
+        $this->assertEquals('foobar', $method->invoke($tfa, 'MZXW6YTBOI'));
+    }
+
+    /**
+     * @return void
+     */
+    public function testKnownTestVectors_sha1()
+    {
+        //Known test vectors for SHA1: https://tools.ietf.org/html/rfc6238#page-15
+        $secret = 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ';   //== base32encode('12345678901234567890')
+        $tfa = new TwoFactorAuth('Test', 8, 30, 'sha1');
+        $this->assertEquals('94287082', $tfa->getCode($secret, 59));
+        $this->assertEquals('07081804', $tfa->getCode($secret, 1111111109));
+        $this->assertEquals('14050471', $tfa->getCode($secret, 1111111111));
+        $this->assertEquals('89005924', $tfa->getCode($secret, 1234567890));
+        $this->assertEquals('69279037', $tfa->getCode($secret, 2000000000));
+        $this->assertEquals('65353130', $tfa->getCode($secret, 20000000000));
+    }
+
+    /**
+     * @return void
+     */
+    public function testKnownTestVectors_sha256()
+    {
+        //Known test vectors for SHA256: https://tools.ietf.org/html/rfc6238#page-15
+        $secret = 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZA';   //== base32encode('12345678901234567890123456789012')
+        $tfa = new TwoFactorAuth('Test', 8, 30, 'sha256');
+        $this->assertEquals('46119246', $tfa->getCode($secret, 59));
+        $this->assertEquals('68084774', $tfa->getCode($secret, 1111111109));
+        $this->assertEquals('67062674', $tfa->getCode($secret, 1111111111));
+        $this->assertEquals('91819424', $tfa->getCode($secret, 1234567890));
+        $this->assertEquals('90698825', $tfa->getCode($secret, 2000000000));
+        $this->assertEquals('77737706', $tfa->getCode($secret, 20000000000));
+    }
+
+    /**
+     * @return void
+     */
+    public function testKnownTestVectors_sha512()
+    {
+        //Known test vectors for SHA512: https://tools.ietf.org/html/rfc6238#page-15
+        $secret = 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNA';   //== base32encode('1234567890123456789012345678901234567890123456789012345678901234')
+        $tfa = new TwoFactorAuth('Test', 8, 30, 'sha512');
+        $this->assertEquals('90693936', $tfa->getCode($secret, 59));
+        $this->assertEquals('25091201', $tfa->getCode($secret, 1111111109));
+        $this->assertEquals('99943326', $tfa->getCode($secret, 1111111111));
+        $this->assertEquals('93441116', $tfa->getCode($secret, 1234567890));
+        $this->assertEquals('38618901', $tfa->getCode($secret, 2000000000));
+        $this->assertEquals('47863826', $tfa->getCode($secret, 20000000000));
+    }
+}

+ 1 - 1
vendor/services.php

@@ -1,5 +1,5 @@
 <?php 
-// This file is automatically generated at:2021-07-01 12:32:27
+// This file is automatically generated at:2021-07-03 23:57:18
 declare (strict_types = 1);
 return array (
   0 => 'think\\captcha\\CaptchaService',