v1.0 Initial commit of project
This commit is contained in:
16
vendor/robthree/twofactorauth/lib/Algorithm.php
vendored
Normal file
16
vendor/robthree/twofactorauth/lib/Algorithm.php
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RobThree\Auth;
|
||||
|
||||
/**
|
||||
* List of supported cryptographic algorithms
|
||||
*/
|
||||
enum Algorithm: string
|
||||
{
|
||||
case Md5 = 'md5';
|
||||
case Sha1 = 'sha1';
|
||||
case Sha256 = 'sha256';
|
||||
case Sha512 = 'sha512';
|
||||
}
|
||||
141
vendor/robthree/twofactorauth/lib/Providers/Qr/BaconQrCodeProvider.php
vendored
Normal file
141
vendor/robthree/twofactorauth/lib/Providers/Qr/BaconQrCodeProvider.php
vendored
Normal file
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RobThree\Auth\Providers\Qr;
|
||||
|
||||
use BaconQrCode\Renderer\Color\Rgb;
|
||||
use BaconQrCode\Renderer\Image\EpsImageBackEnd;
|
||||
use BaconQrCode\Renderer\Image\ImageBackEndInterface;
|
||||
use BaconQrCode\Renderer\Image\ImagickImageBackEnd;
|
||||
use BaconQrCode\Renderer\Image\SvgImageBackEnd;
|
||||
use BaconQrCode\Renderer\ImageRenderer;
|
||||
use BaconQrCode\Renderer\RendererStyle\EyeFill;
|
||||
use BaconQrCode\Renderer\RendererStyle\Fill;
|
||||
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
|
||||
use BaconQrCode\Writer;
|
||||
use RuntimeException;
|
||||
|
||||
class BaconQrCodeProvider implements IQRCodeProvider
|
||||
{
|
||||
/**
|
||||
* Ensure we using the latest Bacon QR Code and specify default options
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly int $borderWidth = 4,
|
||||
private string|array $backgroundColour = '#ffffff',
|
||||
private string|array $foregroundColour = '#000000',
|
||||
private string $format = 'png',
|
||||
) {
|
||||
$this->backgroundColour = $this->handleColour($this->backgroundColour);
|
||||
$this->foregroundColour = $this->handleColour($this->foregroundColour);
|
||||
$this->format = strtolower($this->format);
|
||||
}
|
||||
|
||||
public function getMimeType(): string
|
||||
{
|
||||
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(string $qrText, int $size): string
|
||||
{
|
||||
$backend = match ($this->format) {
|
||||
'svg' => new SvgImageBackEnd(),
|
||||
'eps' => new EpsImageBackEnd(),
|
||||
default => new ImagickImageBackEnd($this->format),
|
||||
};
|
||||
|
||||
$output = $this->getQRCodeByBackend($qrText, $size, $backend);
|
||||
|
||||
if ($this->format === 'svg') {
|
||||
$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(...$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(array|string $colour): array|string
|
||||
{
|
||||
if (is_string($colour) && $colour[0] == '#') {
|
||||
$hexToRGB = static function ($input) {
|
||||
// ensure input no longer has a # for more predictable division
|
||||
// PHP 8.1 does not like implicitly casting a float to an int
|
||||
$input = trim($input, '#');
|
||||
|
||||
if (strlen($input) != 3 && strlen($input) != 6) {
|
||||
throw new RuntimeException('Colour should be a 3 or 6 character value after the #');
|
||||
}
|
||||
|
||||
// split the array into three chunks
|
||||
$split = str_split($input, strlen($input) / 3);
|
||||
|
||||
// cope with three character hex reference
|
||||
if (strlen($input) == 3) {
|
||||
array_walk($split, static 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');
|
||||
}
|
||||
}
|
||||
32
vendor/robthree/twofactorauth/lib/Providers/Qr/BaseHTTPQRCodeProvider.php
vendored
Normal file
32
vendor/robthree/twofactorauth/lib/Providers/Qr/BaseHTTPQRCodeProvider.php
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RobThree\Auth\Providers\Qr;
|
||||
|
||||
abstract class BaseHTTPQRCodeProvider implements IQRCodeProvider
|
||||
{
|
||||
protected bool $verifyssl = true;
|
||||
|
||||
protected function getContent(string $url): string
|
||||
{
|
||||
$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);
|
||||
if ($data === false) {
|
||||
throw new QRException(curl_error($curlhandle));
|
||||
}
|
||||
|
||||
curl_close($curlhandle);
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
123
vendor/robthree/twofactorauth/lib/Providers/Qr/EndroidQrCodeProvider.php
vendored
Normal file
123
vendor/robthree/twofactorauth/lib/Providers/Qr/EndroidQrCodeProvider.php
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RobThree\Auth\Providers\Qr;
|
||||
|
||||
use Endroid\QrCode\Color\Color;
|
||||
use Endroid\QrCode\ErrorCorrectionLevel;
|
||||
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelHigh;
|
||||
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelInterface;
|
||||
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelLow;
|
||||
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelMedium;
|
||||
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelQuartile;
|
||||
use Endroid\QrCode\QrCode;
|
||||
use Endroid\QrCode\Writer\PngWriter;
|
||||
|
||||
class EndroidQrCodeProvider implements IQRCodeProvider
|
||||
{
|
||||
public $bgcolor;
|
||||
|
||||
public $color;
|
||||
|
||||
public $margin;
|
||||
|
||||
public $errorcorrectionlevel;
|
||||
|
||||
protected $endroid4 = false;
|
||||
|
||||
protected $endroid5 = false;
|
||||
|
||||
protected $endroid6 = false;
|
||||
|
||||
public function __construct($bgcolor = 'ffffff', $color = '000000', $margin = 0, $errorcorrectionlevel = 'H')
|
||||
{
|
||||
$this->endroid5 = enum_exists(ErrorCorrectionLevel::class);
|
||||
$this->endroid6 = $this->endroid5 && !method_exists(QrCode::class, 'setSize');
|
||||
$this->endroid4 = $this->endroid6 || method_exists(QrCode::class, 'create');
|
||||
|
||||
$this->bgcolor = $this->handleColor($bgcolor);
|
||||
$this->color = $this->handleColor($color);
|
||||
$this->margin = $margin;
|
||||
$this->errorcorrectionlevel = $this->handleErrorCorrectionLevel($errorcorrectionlevel);
|
||||
}
|
||||
|
||||
public function getMimeType(): string
|
||||
{
|
||||
return 'image/png';
|
||||
}
|
||||
|
||||
public function getQRCodeImage(string $qrText, int $size): string
|
||||
{
|
||||
if (!$this->endroid4) {
|
||||
return $this->qrCodeInstance($qrText, $size)->writeString();
|
||||
}
|
||||
|
||||
$writer = new PngWriter();
|
||||
return $writer->write($this->qrCodeInstance($qrText, $size))->getString();
|
||||
}
|
||||
|
||||
protected function qrCodeInstance(string $qrText, int $size): QrCode
|
||||
{
|
||||
if ($this->endroid6) {
|
||||
return new QrCode(
|
||||
data: $qrText,
|
||||
errorCorrectionLevel: $this->errorcorrectionlevel,
|
||||
size: $size,
|
||||
margin: $this->margin,
|
||||
foregroundColor: $this->color,
|
||||
backgroundColor: $this->bgcolor
|
||||
);
|
||||
}
|
||||
|
||||
$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(string $color): Color|array
|
||||
{
|
||||
$split = str_split($color, 2);
|
||||
$r = hexdec($split[0]);
|
||||
$g = hexdec($split[1]);
|
||||
$b = hexdec($split[2]);
|
||||
|
||||
return $this->endroid4 ? new Color($r, $g, $b, 0) : array('r' => $r, 'g' => $g, 'b' => $b, 'a' => 0);
|
||||
}
|
||||
|
||||
private function handleErrorCorrectionLevel(string $level): ErrorCorrectionLevelInterface|ErrorCorrectionLevel
|
||||
{
|
||||
// First check for version 5 (using enums)
|
||||
if ($this->endroid5) {
|
||||
return match ($level) {
|
||||
'L' => ErrorCorrectionLevel::Low,
|
||||
'M' => ErrorCorrectionLevel::Medium,
|
||||
'Q' => ErrorCorrectionLevel::Quartile,
|
||||
default => ErrorCorrectionLevel::High,
|
||||
};
|
||||
}
|
||||
|
||||
// If not check for version 4 (using classes)
|
||||
if ($this->endroid4) {
|
||||
return match ($level) {
|
||||
'L' => new ErrorCorrectionLevelLow(),
|
||||
'M' => new ErrorCorrectionLevelMedium(),
|
||||
'Q' => new ErrorCorrectionLevelQuartile(),
|
||||
default => new ErrorCorrectionLevelHigh(),
|
||||
};
|
||||
}
|
||||
|
||||
// Any other version will be using strings
|
||||
return match ($level) {
|
||||
'L' => ErrorCorrectionLevel::LOW(),
|
||||
'M' => ErrorCorrectionLevel::MEDIUM(),
|
||||
'Q' => ErrorCorrectionLevel::QUARTILE(),
|
||||
default => ErrorCorrectionLevel::HIGH(),
|
||||
};
|
||||
}
|
||||
}
|
||||
65
vendor/robthree/twofactorauth/lib/Providers/Qr/EndroidQrCodeWithLogoProvider.php
vendored
Normal file
65
vendor/robthree/twofactorauth/lib/Providers/Qr/EndroidQrCodeWithLogoProvider.php
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RobThree\Auth\Providers\Qr;
|
||||
|
||||
use Endroid\QrCode\Logo\Logo;
|
||||
use Endroid\QrCode\QrCode;
|
||||
use Endroid\QrCode\Writer\PngWriter;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public function getQRCodeImage(string $qrText, int $size): string
|
||||
{
|
||||
if (!$this->endroid4) {
|
||||
return $this->qrCodeInstance($qrText, $size)->writeString();
|
||||
}
|
||||
|
||||
$logo = null;
|
||||
if ($this->logoPath) {
|
||||
if ($this->endroid6) {
|
||||
$logo = new Logo($this->logoPath, ...$this->logoSize);
|
||||
} else {
|
||||
$logo = Logo::create($this->logoPath);
|
||||
if ($this->logoSize) {
|
||||
$logo->setResizeToWidth($this->logoSize[0]);
|
||||
if (isset($this->logoSize[1])) {
|
||||
$logo->setResizeToHeight($this->logoSize[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$writer = new PngWriter();
|
||||
return $writer->write($this->qrCodeInstance($qrText, $size), $logo)->getString();
|
||||
}
|
||||
|
||||
protected function qrCodeInstance(string $qrText, int $size): QrCode
|
||||
{
|
||||
$qrCode = parent::qrCodeInstance($qrText, $size);
|
||||
|
||||
if (!$this->endroid4 && $this->logoPath) {
|
||||
$qrCode->setLogoPath($this->logoPath);
|
||||
if ($this->logoSize) {
|
||||
$qrCode->setLogoSize($this->logoSize[0], $this->logoSize[1] ?? null);
|
||||
}
|
||||
}
|
||||
|
||||
return $qrCode;
|
||||
}
|
||||
}
|
||||
36
vendor/robthree/twofactorauth/lib/Providers/Qr/GoogleChartsQrCodeProvider.php
vendored
Normal file
36
vendor/robthree/twofactorauth/lib/Providers/Qr/GoogleChartsQrCodeProvider.php
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RobThree\Auth\Providers\Qr;
|
||||
|
||||
// https://developers.google.com/chart/infographics/docs/qr_codes
|
||||
class GoogleChartsQrCodeProvider extends BaseHTTPQRCodeProvider
|
||||
{
|
||||
public function __construct(protected bool $verifyssl = true, public string $errorcorrectionlevel = 'L', public int $margin = 4, public string $encoding = 'UTF-8')
|
||||
{
|
||||
}
|
||||
|
||||
public function getMimeType(): string
|
||||
{
|
||||
return 'image/png';
|
||||
}
|
||||
|
||||
public function getQRCodeImage(string $qrText, int $size): string
|
||||
{
|
||||
return $this->getContent($this->getUrl($qrText, $size));
|
||||
}
|
||||
|
||||
public function getUrl(string $qrText, int $size): string
|
||||
{
|
||||
$queryParameters = array(
|
||||
'chs' => $size . 'x' . $size,
|
||||
'chld' => strtoupper($this->errorcorrectionlevel) . '|' . $this->margin,
|
||||
'cht' => 'qr',
|
||||
'choe' => $this->encoding,
|
||||
'chl' => $qrText,
|
||||
);
|
||||
|
||||
return 'https://chart.googleapis.com/chart?' . http_build_query($queryParameters);
|
||||
}
|
||||
}
|
||||
27
vendor/robthree/twofactorauth/lib/Providers/Qr/HandlesDataUri.php
vendored
Normal file
27
vendor/robthree/twofactorauth/lib/Providers/Qr/HandlesDataUri.php
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RobThree\Auth\Providers\Qr;
|
||||
|
||||
use function base64_decode;
|
||||
use function preg_match;
|
||||
|
||||
trait HandlesDataUri
|
||||
{
|
||||
/**
|
||||
* @return array<string, string>|null
|
||||
*/
|
||||
private function DecodeDataUri(string $datauri): ?array
|
||||
{
|
||||
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'], true),
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
24
vendor/robthree/twofactorauth/lib/Providers/Qr/IQRCodeProvider.php
vendored
Normal file
24
vendor/robthree/twofactorauth/lib/Providers/Qr/IQRCodeProvider.php
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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(string $qrText, int $size): string;
|
||||
|
||||
/**
|
||||
* Returns the appropriate mime type for the QR code
|
||||
* that will be generated
|
||||
*/
|
||||
public function getMimeType(): string;
|
||||
}
|
||||
37
vendor/robthree/twofactorauth/lib/Providers/Qr/ImageChartsQRCodeProvider.php
vendored
Normal file
37
vendor/robthree/twofactorauth/lib/Providers/Qr/ImageChartsQRCodeProvider.php
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RobThree\Auth\Providers\Qr;
|
||||
|
||||
/**
|
||||
* Use https://image-charts.com to provide a QR code
|
||||
*/
|
||||
class ImageChartsQRCodeProvider extends BaseHTTPQRCodeProvider
|
||||
{
|
||||
public function __construct(protected bool $verifyssl = true, public string $errorcorrectionlevel = 'L', public int $margin = 1)
|
||||
{
|
||||
}
|
||||
|
||||
public function getMimeType(): string
|
||||
{
|
||||
return 'image/png';
|
||||
}
|
||||
|
||||
public function getQRCodeImage(string $qrText, int $size): string
|
||||
{
|
||||
return $this->getContent($this->getUrl($qrText, $size));
|
||||
}
|
||||
|
||||
public function getUrl(string $qrText, int $size): string
|
||||
{
|
||||
$queryParameters = array(
|
||||
'cht' => 'qr',
|
||||
'chs' => ceil($size / 2) . 'x' . ceil($size / 2),
|
||||
'chld' => $this->errorcorrectionlevel . '|' . $this->margin,
|
||||
'chl' => $qrText,
|
||||
);
|
||||
|
||||
return 'https://image-charts.com/chart?' . http_build_query($queryParameters);
|
||||
}
|
||||
}
|
||||
11
vendor/robthree/twofactorauth/lib/Providers/Qr/QRException.php
vendored
Normal file
11
vendor/robthree/twofactorauth/lib/Providers/Qr/QRException.php
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RobThree\Auth\Providers\Qr;
|
||||
|
||||
use RobThree\Auth\TwoFactorAuthException;
|
||||
|
||||
class QRException extends TwoFactorAuthException
|
||||
{
|
||||
}
|
||||
59
vendor/robthree/twofactorauth/lib/Providers/Qr/QRServerProvider.php
vendored
Normal file
59
vendor/robthree/twofactorauth/lib/Providers/Qr/QRServerProvider.php
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RobThree\Auth\Providers\Qr;
|
||||
|
||||
/**
|
||||
* Use https://goqr.me/api/doc/create-qr-code/ to get QR code
|
||||
*/
|
||||
class QRServerProvider extends BaseHTTPQRCodeProvider
|
||||
{
|
||||
public function __construct(protected bool $verifyssl = true, public string $errorcorrectionlevel = 'L', public int $margin = 4, public int $qzone = 1, public string $bgcolor = 'ffffff', public string $color = '000000', public string $format = 'png')
|
||||
{
|
||||
}
|
||||
|
||||
public function getMimeType(): string
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
public function getQRCodeImage(string $qrText, int $size): string
|
||||
{
|
||||
return $this->getContent($this->getUrl($qrText, $size));
|
||||
}
|
||||
|
||||
public function getUrl(string $qrText, int $size): string
|
||||
{
|
||||
$queryParameters = array(
|
||||
'size' => $size . 'x' . $size,
|
||||
'ecc' => strtoupper($this->errorcorrectionlevel),
|
||||
'margin' => $this->margin,
|
||||
'qzone' => $this->qzone,
|
||||
'bgcolor' => $this->decodeColor($this->bgcolor),
|
||||
'color' => $this->decodeColor($this->color),
|
||||
'format' => strtolower($this->format),
|
||||
'data' => $qrText,
|
||||
);
|
||||
|
||||
return 'https://api.qrserver.com/v1/create-qr-code/?' . http_build_query($queryParameters);
|
||||
}
|
||||
|
||||
private function decodeColor(string $value): string
|
||||
{
|
||||
return vsprintf('%d-%d-%d', sscanf($value, '%02x%02x%02x'));
|
||||
}
|
||||
}
|
||||
47
vendor/robthree/twofactorauth/lib/Providers/Qr/QRicketProvider.php
vendored
Normal file
47
vendor/robthree/twofactorauth/lib/Providers/Qr/QRicketProvider.php
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RobThree\Auth\Providers\Qr;
|
||||
|
||||
/**
|
||||
* Use http://qrickit.com/qrickit_apps/qrickit_api.php to provide a QR code
|
||||
*/
|
||||
class QRicketProvider extends BaseHTTPQRCodeProvider
|
||||
{
|
||||
public function __construct(protected bool $verifyssl = true, public string $errorcorrectionlevel = 'L', public string $bgcolor = 'ffffff', public string $color = '000000', public string $format = 'p')
|
||||
{
|
||||
}
|
||||
|
||||
public function getMimeType(): string
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
public function getQRCodeImage(string $qrText, int $size): string
|
||||
{
|
||||
return $this->getContent($this->getUrl($qrText, $size));
|
||||
}
|
||||
|
||||
public function getUrl(string $qrText, int $size): string
|
||||
{
|
||||
$queryParameters = array(
|
||||
'qrsize' => $size,
|
||||
'e' => strtolower($this->errorcorrectionlevel),
|
||||
'bgdcolor' => $this->bgcolor,
|
||||
'fgdcolor' => $this->color,
|
||||
't' => strtolower($this->format),
|
||||
'd' => $qrText,
|
||||
);
|
||||
|
||||
return 'https://qrickit.com/api/qr?' . http_build_query($queryParameters);
|
||||
}
|
||||
}
|
||||
16
vendor/robthree/twofactorauth/lib/Providers/Rng/CSRNGProvider.php
vendored
Normal file
16
vendor/robthree/twofactorauth/lib/Providers/Rng/CSRNGProvider.php
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RobThree\Auth\Providers\Rng;
|
||||
|
||||
class CSRNGProvider implements IRNGProvider
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRandomBytes(int $bytecount): string
|
||||
{
|
||||
return random_bytes($bytecount); // PHP7+
|
||||
}
|
||||
}
|
||||
10
vendor/robthree/twofactorauth/lib/Providers/Rng/IRNGProvider.php
vendored
Normal file
10
vendor/robthree/twofactorauth/lib/Providers/Rng/IRNGProvider.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RobThree\Auth\Providers\Rng;
|
||||
|
||||
interface IRNGProvider
|
||||
{
|
||||
public function getRandomBytes(int $bytecount): string;
|
||||
}
|
||||
11
vendor/robthree/twofactorauth/lib/Providers/Rng/RNGException.php
vendored
Normal file
11
vendor/robthree/twofactorauth/lib/Providers/Rng/RNGException.php
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RobThree\Auth\Providers\Rng;
|
||||
|
||||
use RobThree\Auth\TwoFactorAuthException;
|
||||
|
||||
class RNGException extends TwoFactorAuthException
|
||||
{
|
||||
}
|
||||
62
vendor/robthree/twofactorauth/lib/Providers/Time/HttpTimeProvider.php
vendored
Normal file
62
vendor/robthree/twofactorauth/lib/Providers/Time/HttpTimeProvider.php
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RobThree\Auth\Providers\Time;
|
||||
|
||||
use DateTime;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Takes the time from any webserver by doing a HEAD request on the specified URL and extracting the 'Date:' header
|
||||
*/
|
||||
class HttpTimeProvider implements ITimeProvider
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $options
|
||||
*/
|
||||
public function __construct(
|
||||
public string $url = 'https://google.com',
|
||||
public string $expectedtimeformat = 'D, d M Y H:i:s O+',
|
||||
public ?array $options = null,
|
||||
) {
|
||||
if ($this->options === null) {
|
||||
$this->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',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
13
vendor/robthree/twofactorauth/lib/Providers/Time/ITimeProvider.php
vendored
Normal file
13
vendor/robthree/twofactorauth/lib/Providers/Time/ITimeProvider.php
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RobThree\Auth\Providers\Time;
|
||||
|
||||
interface ITimeProvider
|
||||
{
|
||||
/**
|
||||
* @return int the current timestamp according to this provider
|
||||
*/
|
||||
public function getTime();
|
||||
}
|
||||
13
vendor/robthree/twofactorauth/lib/Providers/Time/LocalMachineTimeProvider.php
vendored
Normal file
13
vendor/robthree/twofactorauth/lib/Providers/Time/LocalMachineTimeProvider.php
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RobThree\Auth\Providers\Time;
|
||||
|
||||
class LocalMachineTimeProvider implements ITimeProvider
|
||||
{
|
||||
public function getTime()
|
||||
{
|
||||
return time();
|
||||
}
|
||||
}
|
||||
58
vendor/robthree/twofactorauth/lib/Providers/Time/NTPTimeProvider.php
vendored
Normal file
58
vendor/robthree/twofactorauth/lib/Providers/Time/NTPTimeProvider.php
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RobThree\Auth\Providers\Time;
|
||||
|
||||
use Exception;
|
||||
|
||||
use function socket_create;
|
||||
|
||||
/**
|
||||
* Takes the time from any NTP server
|
||||
*/
|
||||
class NTPTimeProvider implements ITimeProvider
|
||||
{
|
||||
public function __construct(public string $host = 'time.google.com', public int $port = 123, public int $timeout = 1)
|
||||
{
|
||||
if ($this->port <= 0 || $this->port > 65535) {
|
||||
throw new TimeException('Port must be 0 < port < 65535');
|
||||
}
|
||||
|
||||
if ($this->timeout < 0) {
|
||||
throw new TimeException('Timeout must be >= 0');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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, array('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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
11
vendor/robthree/twofactorauth/lib/Providers/Time/TimeException.php
vendored
Normal file
11
vendor/robthree/twofactorauth/lib/Providers/Time/TimeException.php
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RobThree\Auth\Providers\Time;
|
||||
|
||||
use RobThree\Auth\TwoFactorAuthException;
|
||||
|
||||
class TimeException extends TwoFactorAuthException
|
||||
{
|
||||
}
|
||||
211
vendor/robthree/twofactorauth/lib/TwoFactorAuth.php
vendored
Normal file
211
vendor/robthree/twofactorauth/lib/TwoFactorAuth.php
vendored
Normal file
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RobThree\Auth;
|
||||
|
||||
use function hash_equals;
|
||||
|
||||
use RobThree\Auth\Providers\Qr\IQRCodeProvider;
|
||||
use RobThree\Auth\Providers\Rng\CSRNGProvider;
|
||||
use RobThree\Auth\Providers\Rng\IRNGProvider;
|
||||
use RobThree\Auth\Providers\Time\HttpTimeProvider;
|
||||
use RobThree\Auth\Providers\Time\ITimeProvider;
|
||||
use RobThree\Auth\Providers\Time\LocalMachineTimeProvider;
|
||||
use RobThree\Auth\Providers\Time\NTPTimeProvider;
|
||||
use SensitiveParameter;
|
||||
|
||||
// Based on / inspired by: https://github.com/PHPGangsta/GoogleAuthenticator
|
||||
// Algorithms, digits, period etc. explained: https://github.com/google/google-authenticator/wiki/Key-Uri-Format
|
||||
class TwoFactorAuth
|
||||
{
|
||||
private static string $_base32dict = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=';
|
||||
|
||||
/** @var array<string> */
|
||||
private static array $_base32;
|
||||
|
||||
/** @var array<string, int> */
|
||||
private static array $_base32lookup = array();
|
||||
|
||||
public function __construct(
|
||||
private IQRCodeProvider $qrcodeprovider,
|
||||
private readonly ?string $issuer = null,
|
||||
private readonly int $digits = 6,
|
||||
private readonly int $period = 30,
|
||||
private readonly Algorithm $algorithm = Algorithm::Sha1,
|
||||
private ?IRNGProvider $rngprovider = null,
|
||||
private ?ITimeProvider $timeprovider = null
|
||||
) {
|
||||
if ($this->digits <= 0) {
|
||||
throw new TwoFactorAuthException('Digits must be > 0');
|
||||
}
|
||||
|
||||
if ($this->period <= 0) {
|
||||
throw new TwoFactorAuthException('Period must be int > 0');
|
||||
}
|
||||
|
||||
self::$_base32 = str_split(self::$_base32dict);
|
||||
self::$_base32lookup = array_flip(self::$_base32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new secret
|
||||
*/
|
||||
public function createSecret(int $bits = 160): string
|
||||
{
|
||||
$secret = '';
|
||||
$bytes = (int)ceil($bits / 5); // We use 5 bits of each byte (since we have a 32-character 'alphabet' / BASE32)
|
||||
$rngprovider = $this->getRngProvider();
|
||||
$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
|
||||
*/
|
||||
public function getCode(#[SensitiveParameter] string $secret, ?int $time = null): string
|
||||
{
|
||||
$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->value, $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 % 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
|
||||
*/
|
||||
public function verifyCode(string $secret, string $code, int $discrepancy = 1, ?int $time = null, ?int &$timeslice = 0): bool
|
||||
{
|
||||
$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 = hash_equals($this->getCode($secret, $ts), $code) ? $slice : $timeslice;
|
||||
}
|
||||
|
||||
return $timeslice > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data-uri of QRCode
|
||||
*/
|
||||
public function getQRCodeImageAsDataUri(string $label, #[SensitiveParameter] string $secret, int $size = 200): string
|
||||
{
|
||||
if ($size <= 0) {
|
||||
throw new TwoFactorAuthException('Size must be > 0');
|
||||
}
|
||||
|
||||
return 'data:'
|
||||
. $this->qrcodeprovider->getMimeType()
|
||||
. ';base64,'
|
||||
. base64_encode($this->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<ITimeProvider> $timeproviders
|
||||
* @throws TwoFactorAuthException
|
||||
*/
|
||||
public function ensureCorrectTime(?array $timeproviders = null, int $leniency = 5): void
|
||||
{
|
||||
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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a string to be encoded in a QR code
|
||||
*/
|
||||
public function getQRText(string $label, #[SensitiveParameter] string $secret): string
|
||||
{
|
||||
return 'otpauth://totp/' . rawurlencode($label)
|
||||
. '?secret=' . rawurlencode($secret)
|
||||
. '&issuer=' . rawurlencode((string)$this->issuer)
|
||||
. '&period=' . $this->period
|
||||
. '&algorithm=' . rawurlencode(strtoupper($this->algorithm->value))
|
||||
. '&digits=' . $this->digits;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws TwoFactorAuthException
|
||||
*/
|
||||
public function getRngProvider(): IRNGProvider
|
||||
{
|
||||
return $this->rngprovider ??= new CSRNGProvider();
|
||||
}
|
||||
|
||||
public function getTimeProvider(): ITimeProvider
|
||||
{
|
||||
// Set default time provider if none was specified
|
||||
return $this->timeprovider ??= new LocalMachineTimeProvider();
|
||||
}
|
||||
|
||||
private function getTime(?int $time = null): int
|
||||
{
|
||||
return $time ?? $this->getTimeProvider()->getTime();
|
||||
}
|
||||
|
||||
private function getTimeSlice(?int $time = null, int $offset = 0): int
|
||||
{
|
||||
return (int)floor($time / $this->period) + ($offset * $this->period);
|
||||
}
|
||||
|
||||
private function base32Decode(string $value): string
|
||||
{
|
||||
if ($value === '') {
|
||||
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;
|
||||
}
|
||||
}
|
||||
11
vendor/robthree/twofactorauth/lib/TwoFactorAuthException.php
vendored
Normal file
11
vendor/robthree/twofactorauth/lib/TwoFactorAuthException.php
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RobThree\Auth;
|
||||
|
||||
use Exception;
|
||||
|
||||
class TwoFactorAuthException extends Exception
|
||||
{
|
||||
}
|
||||
Reference in New Issue
Block a user