From 6c29b298e2cfdc0f21ccdec2d92506c93093911c Mon Sep 17 00:00:00 2001 From: Ilham Firdausi Putra <ilhamfputra31@gmail.com> Date: Fri, 30 Nov 2018 14:45:54 +0700 Subject: [PATCH] add token input to book detail --- app/controllers/Detail.php | 2 +- app/models/SoapHelper.php | 4 +- app/models/Token.php | 2 + app/views/detail.php | 7 +- composer.json | 5 + composer.lock | 75 +++ example.php | 41 ++ index.php | 1 + public/css/main.css | 70 +++ public/images/profile/32 | Bin 0 -> 21876 bytes public/js/detail.js | 14 +- public/js/profile.js | 11 + vendor/autoload.php | 7 + vendor/composer/ClassLoader.php | 445 ++++++++++++++++++ vendor/composer/LICENSE | 21 + vendor/composer/autoload_classmap.php | 9 + vendor/composer/autoload_namespaces.php | 9 + vendor/composer/autoload_psr4.php | 11 + vendor/composer/autoload_real.php | 52 ++ vendor/composer/autoload_static.php | 39 ++ vendor/composer/installed.json | 61 +++ .../google-authenticator/LICENSE | 21 + .../google-authenticator/Makefile | 60 +++ .../google-authenticator/composer.json | 50 ++ .../google-authenticator/phpunit.xml.dist | 40 ++ .../google-authenticator/sample/example.php | 41 ++ .../sample/tmpl/ask-for-otp.php | 23 + .../sample/tmpl/loggedin.php | 19 + .../sample/tmpl/login-error.php | 6 + .../sample/tmpl/login.php | 8 + .../sample/tmpl/show-qr.php | 11 + .../google-authenticator/sample/users.dat | 1 + .../google-authenticator/sample/web/Users.php | 155 ++++++ .../google-authenticator/sample/web/index.php | 119 +++++ .../src/FixedBitNotation.php | 296 ++++++++++++ .../src/GoogleAuthenticator.php | 173 +++++++ .../src/GoogleAuthenticatorInterface.php | 44 ++ .../google-authenticator/src/GoogleQrUrl.php | 94 ++++ .../src/RuntimeException.php | 46 ++ 39 files changed, 2088 insertions(+), 5 deletions(-) create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 example.php create mode 100644 public/images/profile/32 create mode 100644 public/js/profile.js create mode 100644 vendor/autoload.php create mode 100644 vendor/composer/ClassLoader.php create mode 100644 vendor/composer/LICENSE create mode 100644 vendor/composer/autoload_classmap.php create mode 100644 vendor/composer/autoload_namespaces.php create mode 100644 vendor/composer/autoload_psr4.php create mode 100644 vendor/composer/autoload_real.php create mode 100644 vendor/composer/autoload_static.php create mode 100644 vendor/composer/installed.json create mode 100644 vendor/sonata-project/google-authenticator/LICENSE create mode 100644 vendor/sonata-project/google-authenticator/Makefile create mode 100644 vendor/sonata-project/google-authenticator/composer.json create mode 100644 vendor/sonata-project/google-authenticator/phpunit.xml.dist create mode 100644 vendor/sonata-project/google-authenticator/sample/example.php create mode 100644 vendor/sonata-project/google-authenticator/sample/tmpl/ask-for-otp.php create mode 100644 vendor/sonata-project/google-authenticator/sample/tmpl/loggedin.php create mode 100644 vendor/sonata-project/google-authenticator/sample/tmpl/login-error.php create mode 100644 vendor/sonata-project/google-authenticator/sample/tmpl/login.php create mode 100644 vendor/sonata-project/google-authenticator/sample/tmpl/show-qr.php create mode 100644 vendor/sonata-project/google-authenticator/sample/users.dat create mode 100644 vendor/sonata-project/google-authenticator/sample/web/Users.php create mode 100644 vendor/sonata-project/google-authenticator/sample/web/index.php create mode 100644 vendor/sonata-project/google-authenticator/src/FixedBitNotation.php create mode 100644 vendor/sonata-project/google-authenticator/src/GoogleAuthenticator.php create mode 100644 vendor/sonata-project/google-authenticator/src/GoogleAuthenticatorInterface.php create mode 100644 vendor/sonata-project/google-authenticator/src/GoogleQrUrl.php create mode 100644 vendor/sonata-project/google-authenticator/src/RuntimeException.php diff --git a/app/controllers/Detail.php b/app/controllers/Detail.php index 2441e0d..6799ca9 100644 --- a/app/controllers/Detail.php +++ b/app/controllers/Detail.php @@ -66,7 +66,7 @@ class Detail extends Controller $entityBody = json_decode(file_get_contents('php://input'), true); //TODO MASUKKIN KARTU NASABAH ID $user = $model_user->readUserById($user_id); - $orderid = $soap->buyBook($_COOKIE['bookid'], $entityBody['total'], $user['no_kartu']); + $orderid = $soap->buyBook($_COOKIE['bookid'], $entityBody['total'], $user['no_kartu'], $entityBody['token']); if ($orderid != -1) { $model->createOrder($_COOKIE['bookid'], $user_id, $orderid); } diff --git a/app/models/SoapHelper.php b/app/models/SoapHelper.php index e391860..bac9da1 100644 --- a/app/models/SoapHelper.php +++ b/app/models/SoapHelper.php @@ -49,8 +49,8 @@ class SoapHelper { return $output; } - public function buyBook($id, $quantity, $no_rek) { - $data = $this->conn->buyBook($id, $quantity, $no_rek); + public function buyBook($id, $quantity, $no_rek, $token) { + $data = $this->conn->buyBook($id, $quantity, $no_rek, $token); $array = json_decode(json_encode($data), True); return $array; } diff --git a/app/models/Token.php b/app/models/Token.php index 5828e97..0bbed2b 100644 --- a/app/models/Token.php +++ b/app/models/Token.php @@ -24,6 +24,8 @@ class Token extends Model $ip_address = $_SERVER['REMOTE_ADDR']; $result = $this->conn->query($sql)->fetch_assoc(); if ($result['id'] && $result['browser'] == $browser && $result['ip_address'] == $ip_address && time() < (int)$result['time']) { + $sql = "UPDATE access_token SET time = " . (time() + (int)1200) . " WHERE token = '" . $token . "';" ; + $this->conn->query($sql); return $result['id']; } else { return null; diff --git a/app/views/detail.php b/app/views/detail.php index 6ca7e33..ca4457c 100644 --- a/app/views/detail.php +++ b/app/views/detail.php @@ -52,6 +52,11 @@ <?php } ?> </select> </div> + <label for="token" class="inp"> + <input type="text" id="token" placeholder=" "> + <span class="label">Token</span> + <span class="border"></span> + </label> <button onclick="order()" <?php if ($data['book']['price'] == -1) {echo "disabled";}?>>Order</button> </section> <section id="reviews"> @@ -98,7 +103,7 @@ <div id="dialog-msg"> <img id="check-notif" src="/public/images/check.png"> <div id="msg-detail"> - <p>Pemesanan Berhasil!</p> + <p id="text-msg-detail">Pemesanan Berhasil!</p> <p>Nomor Transaksi : <span id="transaction-number">3<span></p> </div> <img id="close-notif" onclick="close_notif()" src="/public/images/close.png"> diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..707632d --- /dev/null +++ b/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "sonata-project/google-authenticator": "^2.2" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..f36ec21 --- /dev/null +++ b/composer.lock @@ -0,0 +1,75 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "e2df69fcfa963bd7ffe8e358d7e93d70", + "packages": [ + { + "name": "sonata-project/google-authenticator", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/sonata-project/GoogleAuthenticator.git", + "reference": "feda53899b26af24e3db2fe7a3e5f053ca483762" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sonata-project/GoogleAuthenticator/zipball/feda53899b26af24e3db2fe7a3e5f053ca483762", + "reference": "feda53899b26af24e3db2fe7a3e5f053ca483762", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Google\\Authenticator\\": "src/", + "Sonata\\GoogleAuthenticator\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Stocker", + "email": "me@chregu.tv" + }, + { + "name": "Andre DeMarre", + "homepage": "http://www.devnetwork.net/viewtopic.php?f=50&t=94989" + }, + { + "name": "Thomas Rabaix", + "email": "thomas.rabaix@gmail.com" + } + ], + "description": "Library to integrate Google Authenticator into a PHP project", + "homepage": "https://github.com/sonata-project/GoogleAuthenticator", + "keywords": [ + "google authenticator" + ], + "time": "2018-07-18T22:08:02+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/example.php b/example.php new file mode 100644 index 0000000..50366d2 --- /dev/null +++ b/example.php @@ -0,0 +1,41 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the Sonata Project package. + * + * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +include_once __DIR__.'/../src/FixedBitNotation.php'; +include_once __DIR__.'/../src/GoogleAuthenticator.php'; +include_once __DIR__.'/../src/GoogleQrUrl.php'; + +$secret = 'XVQ2UIGO75XRUKJO'; +$code = '846474'; + +$g = new \Sonata\GoogleAuthenticator\GoogleAuthenticator(); + +echo 'Current Code is: '; +echo $g->getCode($secret); + +echo "\n"; + +echo "Check if $code is valid: "; + +if ($g->checkCode($secret, $code)) { + echo "YES \n"; +} else { + echo "NO \n"; +} + +$secret = $g->generateSecret(); +echo "Get a new Secret: $secret \n"; +echo "The QR Code for this secret (to scan with the Google Authenticator App: \n"; + +echo \Sonata\GoogleAuthenticator\GoogleQrUrl::generate('chregu', $secret, 'GoogleAuthenticatorExample'); +echo "\n"; diff --git a/index.php b/index.php index afa4a1a..996aa32 100644 --- a/index.php +++ b/index.php @@ -1,5 +1,6 @@ <?php ini_set('display_errors', 0); +require_once 'vendor/autoload.php'; require_once 'app/init.php'; diff --git a/public/css/main.css b/public/css/main.css index 7bfd34b..c9ce394 100644 --- a/public/css/main.css +++ b/public/css/main.css @@ -1034,4 +1034,74 @@ body { height: 200px !important; -webkit-transform: translate(-100px, -100px) scale(1) translate(100px, 100px); transform: translate(-100px, -100px) scale(1) translate(100px, 100px); +} + +.qr-code{ + display: block; + text-align: center; + width: 100%; + height: 100%; + margin: auto; + border-radius: 3px; +} + +* .inp { + position: relative; + margin: auto; + width: 100%; + max-width: 280px; +} +* .inp .label { + position: absolute; + top: 16px; + left: 0; + font-size: 16px; + color: #9098a9; + font-weight: 500; + transform-origin: 0 0; + transition: all 0.2s ease; +} +* .inp .border { + position: absolute; + bottom: 0; + left: 0; + height: 2px; + width: 100%; + background: #07f; + transform: scaleX(0); + transform-origin: 0 0; + transition: all 0.15s ease; +} +* .inp input { + -webkit-appearance: none; + width: 100%; + border: 0; + font-family: inherit; + padding: 12px 0; + height: 48px; + font-size: 16px; + font-weight: 500; + border-bottom: 2px solid #c8ccd4; + background: none; + border-radius: 0; + color: #223254; + transition: all 0.15s ease; +} +* .inp input:hover { + background: rgba(34,50,84,0.03); +} +* .inp input:not(:placeholder-shown) + span { + color: #5a667f; + transform: translateY(-26px) scale(0.75); +} +* .inp input:focus { + background: none; + outline: none; +} +* .inp input:focus + span { + color: #07f; + transform: translateY(-26px) scale(0.75); +} +* .inp input:focus + span + .border { + transform: scaleX(1); } \ No newline at end of file diff --git a/public/images/profile/32 b/public/images/profile/32 new file mode 100644 index 0000000000000000000000000000000000000000..27cac80db760b8d73f20e7d0401ee5f3b1e5e530 GIT binary patch literal 21876 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 literal 0 HcmV?d00001 diff --git a/public/js/detail.js b/public/js/detail.js index f3d3a3c..c0ddcce 100644 --- a/public/js/detail.js +++ b/public/js/detail.js @@ -1,5 +1,7 @@ function order() { var total_order_element = document.getElementById("total-order"); + var token = document.getElementById("token").value; + console.log("token: "+token); var sum_order = total_order_element.options[total_order_element.selectedIndex].value; var xhttp = new XMLHttpRequest(); xhttp.open("POST", "/detail/order"); @@ -7,11 +9,21 @@ function order() { xhttp.onreadystatechange = function() { console.log(this.responseText); if (this.readyState == 4 && this.status == 200 && this.responseText != -1) { + document.getElementById("text-msg-detail").innerHTML = "Pemesanan Berhasil!"; document.getElementById("transaction-number").innerHTML = this.responseText; document.getElementById("notif").setAttribute("style", "display: flex"); + document.getElementById("check-notif").src = "/public/images/check.png"; + } else { + document.getElementById("text-msg-detail").innerHTML = "Pemesanan Gagal!"; + document.getElementById("transaction-number").innerHTML = -1; + document.getElementById("notif").setAttribute("style", "display: flex"); + document.getElementById("check-notif").src = "/public/images/close.png"; + } } - xhttp.send(JSON.stringify({total : sum_order})); + xhttp.send(JSON.stringify({ + total : sum_order, + token : token})); } function close_notif() { diff --git a/public/js/profile.js b/public/js/profile.js new file mode 100644 index 0000000..d26153e --- /dev/null +++ b/public/js/profile.js @@ -0,0 +1,11 @@ +// console.log("No kartu: " + document.getElementById("no-kartu").value); +var xhttp = new XMLHttpRequest(); +xhttp.onreadystatechange = function() { + if (this.readyState == 4 && this.status == 200) { + var response = JSON.parse(this.responseText); + document.getElementById("qr-img").src = response.values + } +} +xhttp.open("POST", "http://localhost:3000/qrcode", true); +xhttp.setRequestHeader("Content-type", "application/json"); +xhttp.send(JSON.stringify({ "no_kartu": document.getElementById("no-kartu").value })); diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 0000000..46ffb4c --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,7 @@ +<?php + +// autoload.php @generated by Composer + +require_once __DIR__ . '/composer/autoload_real.php'; + +return ComposerAutoloaderInitae14d3cd7eb3d58ce5dbc9a906ade70c::getLoader(); diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php new file mode 100644 index 0000000..fce8549 --- /dev/null +++ b/vendor/composer/ClassLoader.php @@ -0,0 +1,445 @@ +<?php + +/* + * This file is part of Composer. + * + * (c) Nils Adermann <naderman@naderman.de> + * Jordi Boggiano <j.boggiano@seld.be> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier <fabien@symfony.com> + * @author Jordi Boggiano <j.boggiano@seld.be> + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +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. + diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..7a91153 --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,9 @@ +<?php + +// autoload_classmap.php @generated by Composer + +$vendorDir = dirname(dirname(__FILE__)); +$baseDir = dirname($vendorDir); + +return array( +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..b7fc012 --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ +<?php + +// autoload_namespaces.php @generated by Composer + +$vendorDir = dirname(dirname(__FILE__)); +$baseDir = dirname($vendorDir); + +return array( +); diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php new file mode 100644 index 0000000..c57cc2d --- /dev/null +++ b/vendor/composer/autoload_psr4.php @@ -0,0 +1,11 @@ +<?php + +// autoload_psr4.php @generated by Composer + +$vendorDir = dirname(dirname(__FILE__)); +$baseDir = dirname($vendorDir); + +return array( + 'Sonata\\GoogleAuthenticator\\' => array($vendorDir . '/sonata-project/google-authenticator/src'), + 'Google\\Authenticator\\' => array($vendorDir . '/sonata-project/google-authenticator/src'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000..6cd5c1f --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,52 @@ +<?php + +// autoload_real.php @generated by Composer + +class ComposerAutoloaderInitae14d3cd7eb3d58ce5dbc9a906ade70c +{ + private static $loader; + + public static function loadClassLoader($class) + { + if ('Composer\Autoload\ClassLoader' === $class) { + require __DIR__ . '/ClassLoader.php'; + } + } + + public static function getLoader() + { + if (null !== self::$loader) { + return self::$loader; + } + + spl_autoload_register(array('ComposerAutoloaderInitae14d3cd7eb3d58ce5dbc9a906ade70c', 'loadClassLoader'), true, true); + self::$loader = $loader = new \Composer\Autoload\ClassLoader(); + spl_autoload_unregister(array('ComposerAutoloaderInitae14d3cd7eb3d58ce5dbc9a906ade70c', 'loadClassLoader')); + + $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInitae14d3cd7eb3d58ce5dbc9a906ade70c::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + return $loader; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 0000000..20899b7 --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,39 @@ +<?php + +// autoload_static.php @generated by Composer + +namespace Composer\Autoload; + +class ComposerStaticInitae14d3cd7eb3d58ce5dbc9a906ade70c +{ + public static $prefixLengthsPsr4 = array ( + 'S' => + array ( + 'Sonata\\GoogleAuthenticator\\' => 27, + ), + 'G' => + array ( + 'Google\\Authenticator\\' => 21, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Sonata\\GoogleAuthenticator\\' => + array ( + 0 => __DIR__ . '/..' . '/sonata-project/google-authenticator/src', + ), + 'Google\\Authenticator\\' => + array ( + 0 => __DIR__ . '/..' . '/sonata-project/google-authenticator/src', + ), + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInitae14d3cd7eb3d58ce5dbc9a906ade70c::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInitae14d3cd7eb3d58ce5dbc9a906ade70c::$prefixDirsPsr4; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 0000000..b6b2711 --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,61 @@ +[ + { + "name": "sonata-project/google-authenticator", + "version": "2.2.0", + "version_normalized": "2.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sonata-project/GoogleAuthenticator.git", + "reference": "feda53899b26af24e3db2fe7a3e5f053ca483762" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sonata-project/GoogleAuthenticator/zipball/feda53899b26af24e3db2fe7a3e5f053ca483762", + "reference": "feda53899b26af24e3db2fe7a3e5f053ca483762", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.0" + }, + "time": "2018-07-18T22:08:02+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Google\\Authenticator\\": "src/", + "Sonata\\GoogleAuthenticator\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Stocker", + "email": "me@chregu.tv" + }, + { + "name": "Andre DeMarre", + "homepage": "http://www.devnetwork.net/viewtopic.php?f=50&t=94989" + }, + { + "name": "Thomas Rabaix", + "email": "thomas.rabaix@gmail.com" + } + ], + "description": "Library to integrate Google Authenticator into a PHP project", + "homepage": "https://github.com/sonata-project/GoogleAuthenticator", + "keywords": [ + "google authenticator" + ] + } +] diff --git a/vendor/sonata-project/google-authenticator/LICENSE b/vendor/sonata-project/google-authenticator/LICENSE new file mode 100644 index 0000000..0135e0b --- /dev/null +++ b/vendor/sonata-project/google-authenticator/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2010 Thomas Rabaix + +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. diff --git a/vendor/sonata-project/google-authenticator/Makefile b/vendor/sonata-project/google-authenticator/Makefile new file mode 100644 index 0000000..4ae0c63 --- /dev/null +++ b/vendor/sonata-project/google-authenticator/Makefile @@ -0,0 +1,60 @@ +# DO NOT EDIT THIS FILE! +# +# It's auto-generated by sonata-project/dev-kit package. + +all: + @echo "Please choose a task." +.PHONY: all + +lint: lint-composer lint-yaml lint-composer lint-xml lint-php +.PHONY: lint + +lint-composer: + composer validate +.PHONY: lint-composer + +lint-yaml: + yaml-lint --ignore-non-yaml-files --quiet --exclude vendor . + +.PHONY: lint-yaml + +lint-xml: + find . \( -name '*.xml' -or -name '*.xliff' \) \ + -not -path './vendor/*' \ + -not -path './src/Resources/public/vendor/*' \ + | while read xmlFile; \ + do \ + XMLLINT_INDENT=' ' xmllint --encode UTF-8 --format "$$xmlFile"|diff - "$$xmlFile"; \ + if [ $$? -ne 0 ] ;then exit 1; fi; \ + done + +.PHONY: lint-xml + +lint-php: + php-cs-fixer fix --ansi --verbose --diff --dry-run +.PHONY: lint-php + +cs-fix: cs-fix-php cs-fix-xml +.PHONY: cs-fix + +cs-fix-php: + php-cs-fixer fix --verbose +.PHONY: cs-fix-php + +cs-fix-xml: + find . \( -name '*.xml' -or -name '*.xliff' \) \ + -not -path './vendor/*' \ + -not -path './src/Resources/public/vendor/*' \ + | while read xmlFile; \ + do \ + XMLLINT_INDENT=' ' xmllint --encode UTF-8 --format "$$xmlFile" --output "$$xmlFile"; \ + done +.PHONY: cs-fix-xml + +test: + phpunit -c phpunit.xml.dist --coverage-clover build/logs/clover.xml +.PHONY: test + +docs: + cd docs && sphinx-build -W -b html -d _build/doctrees . _build/html +.PHONY: docs diff --git a/vendor/sonata-project/google-authenticator/composer.json b/vendor/sonata-project/google-authenticator/composer.json new file mode 100644 index 0000000..4fbb160 --- /dev/null +++ b/vendor/sonata-project/google-authenticator/composer.json @@ -0,0 +1,50 @@ +{ + "name": "sonata-project/google-authenticator", + "type": "library", + "description": "Library to integrate Google Authenticator into a PHP project", + "keywords": [ + "google authenticator" + ], + "homepage": "https://github.com/sonata-project/GoogleAuthenticator", + "license": "MIT", + "authors": [ + { + "name": "Thomas Rabaix", + "email": "thomas.rabaix@gmail.com" + }, + { + "name": "Christian Stocker", + "email": "me@chregu.tv" + }, + { + "name": "Andre DeMarre", + "homepage": "http://www.devnetwork.net/viewtopic.php?f=50&t=94989" + } + ], + "require": { + "php": "^7.1" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.0" + }, + "config": { + "sort-packages": true + }, + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Google\\Authenticator\\": "src/", + "Sonata\\GoogleAuthenticator\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Google\\Authenticator\\Tests\\": "tests/", + "Sonata\\GoogleAuthenticator\\Tests\\": "tests/" + } + } +} diff --git a/vendor/sonata-project/google-authenticator/phpunit.xml.dist b/vendor/sonata-project/google-authenticator/phpunit.xml.dist new file mode 100644 index 0000000..492b036 --- /dev/null +++ b/vendor/sonata-project/google-authenticator/phpunit.xml.dist @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- +DO NOT EDIT THIS FILE! + +It's auto-generated by sonata-project/dev-kit package. +--> + +<phpunit backupGlobals="false" + backupStaticAttributes="false" + colors="true" + convertErrorsToExceptions="true" + convertNoticesToExceptions="true" + convertWarningsToExceptions="true" + processIsolation="false" + stopOnFailure="false" + syntaxCheck="false" + bootstrap="tests/bootstrap.php" +> + <testsuites> + <testsuite name="Sonata Google Authenticator Test Suite"> + <directory suffix="Test.php">./tests/</directory> + </testsuite> + </testsuites> + + <listeners> + <listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" /> + </listeners> + + <filter> + <whitelist> + <directory suffix=".php">./src/</directory> + </whitelist> + </filter> + + <php> + <ini name="precision" value="8"/> + </php> + +</phpunit> diff --git a/vendor/sonata-project/google-authenticator/sample/example.php b/vendor/sonata-project/google-authenticator/sample/example.php new file mode 100644 index 0000000..50366d2 --- /dev/null +++ b/vendor/sonata-project/google-authenticator/sample/example.php @@ -0,0 +1,41 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the Sonata Project package. + * + * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +include_once __DIR__.'/../src/FixedBitNotation.php'; +include_once __DIR__.'/../src/GoogleAuthenticator.php'; +include_once __DIR__.'/../src/GoogleQrUrl.php'; + +$secret = 'XVQ2UIGO75XRUKJO'; +$code = '846474'; + +$g = new \Sonata\GoogleAuthenticator\GoogleAuthenticator(); + +echo 'Current Code is: '; +echo $g->getCode($secret); + +echo "\n"; + +echo "Check if $code is valid: "; + +if ($g->checkCode($secret, $code)) { + echo "YES \n"; +} else { + echo "NO \n"; +} + +$secret = $g->generateSecret(); +echo "Get a new Secret: $secret \n"; +echo "The QR Code for this secret (to scan with the Google Authenticator App: \n"; + +echo \Sonata\GoogleAuthenticator\GoogleQrUrl::generate('chregu', $secret, 'GoogleAuthenticatorExample'); +echo "\n"; diff --git a/vendor/sonata-project/google-authenticator/sample/tmpl/ask-for-otp.php b/vendor/sonata-project/google-authenticator/sample/tmpl/ask-for-otp.php new file mode 100644 index 0000000..f3e06d4 --- /dev/null +++ b/vendor/sonata-project/google-authenticator/sample/tmpl/ask-for-otp.php @@ -0,0 +1,23 @@ + +<h1>please otp</h1> +<p> +<form method="post" action="./"> +<?php if ($debug) { + ?> + <br/> + (Set $debug in index.php to false, if you don't want to have the OTP prefilled (for real life application, for example ;))<br/> +<?php +} +?> + +otp: <input name="otp" +value="<?php +if ($debug) { + $g = new GoogleAuthenticator(); + echo $g->getCode($user->getSecret()); +} +?>"/><br/> +<input type="checkbox" name="remember" id="remember" /><label for="remember"> Remember verification for this computer for 1 day.</label> <br/> +<input type="submit"/> + +</form> diff --git a/vendor/sonata-project/google-authenticator/sample/tmpl/loggedin.php b/vendor/sonata-project/google-authenticator/sample/tmpl/loggedin.php new file mode 100644 index 0000000..2a19032 --- /dev/null +++ b/vendor/sonata-project/google-authenticator/sample/tmpl/loggedin.php @@ -0,0 +1,19 @@ + +<p> +Hello <?php echo $user->getUsername(); ?> +</p> +<?php +if (!isset($_GET['showqr'])) { + ?> + +<p> +<a href="?showqr=1">Show QR Code</a> +</p> + +<?php +} +?> + +<p> +<a href="?logout=1">Logout</a> +</p> diff --git a/vendor/sonata-project/google-authenticator/sample/tmpl/login-error.php b/vendor/sonata-project/google-authenticator/sample/tmpl/login-error.php new file mode 100644 index 0000000..8d23fd3 --- /dev/null +++ b/vendor/sonata-project/google-authenticator/sample/tmpl/login-error.php @@ -0,0 +1,6 @@ +<p> +Wrong username or password or token. +</p> +<p> +<a href="./">try again</a> +</p> diff --git a/vendor/sonata-project/google-authenticator/sample/tmpl/login.php b/vendor/sonata-project/google-authenticator/sample/tmpl/login.php new file mode 100644 index 0000000..fd81623 --- /dev/null +++ b/vendor/sonata-project/google-authenticator/sample/tmpl/login.php @@ -0,0 +1,8 @@ + +<h1>please login</h1> +<p> +<form method="post" action="./"> +username: <input name="username"/><br/> +password: <input name="password" type="password"/><br/> +<input type="submit"/> +</form> diff --git a/vendor/sonata-project/google-authenticator/sample/tmpl/show-qr.php b/vendor/sonata-project/google-authenticator/sample/tmpl/show-qr.php new file mode 100644 index 0000000..774a298 --- /dev/null +++ b/vendor/sonata-project/google-authenticator/sample/tmpl/show-qr.php @@ -0,0 +1,11 @@ +<h1>Please scan this </h1> + +<p> with <a href="http://www.google.com/support/a/bin/answer.py?hl=en&answer=1037451">the Google Authenticator App</a></p> + +<p> +<?php +$link = \Sonata\GoogleAuthenticator\GoogleQrUrl::generate($user->getUsername(), $secret, 'GoogleAuthenticatorExample'); +?> + +<a href="<?php echo $link; ?>"><img style="border: 0; padding:10px" src="<?php echo $link; ?>"/></a> +</p> diff --git a/vendor/sonata-project/google-authenticator/sample/users.dat b/vendor/sonata-project/google-authenticator/sample/users.dat new file mode 100644 index 0000000..fdcc130 --- /dev/null +++ b/vendor/sonata-project/google-authenticator/sample/users.dat @@ -0,0 +1 @@ +{"chregu":{"password":"foobar"}} \ No newline at end of file diff --git a/vendor/sonata-project/google-authenticator/sample/web/Users.php b/vendor/sonata-project/google-authenticator/sample/web/Users.php new file mode 100644 index 0000000..fb169de --- /dev/null +++ b/vendor/sonata-project/google-authenticator/sample/web/Users.php @@ -0,0 +1,155 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the Sonata Project package. + * + * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +class Users +{ + public function __construct(string $file = '../users.dat') + { + $this->userFile = $file; + + $this->users = json_decode(file_get_contents($file), true); + } + + public function hasSession() + { + session_start(); + if (isset($_SESSION['username'])) { + return $_SESSION['username']; + } + + return false; + } + + public function storeData(User $user): void + { + $this->users[$user->getUsername()] = $user->getData(); + file_put_contents($this->userFile, json_encode($this->users)); + } + + public function loadUser($name) + { + if (isset($this->users[$name])) { + return new User($name, $this->users[$name]); + } + + return false; + } +} + +class User +{ + public function __construct($user, $data) + { + $this->data = $data; + $this->user = $user; + } + + public function auth($pass) + { + if ($this->data['password'] === $pass) { + return true; + } + + return false; + } + + public function startSession(): void + { + $_SESSION['username'] = $this->user; + } + + public function doLogin(): void + { + session_regenerate_id(); + $_SESSION['loggedin'] = true; + $_SESSION['ua'] = $_SERVER['HTTP_USER_AGENT']; + } + + public function doOTP(): void + { + $_SESSION['OTP'] = true; + } + + public function isOTP() + { + if (isset($_SESSION['OTP']) && true == $_SESSION['OTP']) { + return true; + } + + return false; + } + + public function isLoggedIn() + { + if (isset($_SESSION['loggedin']) && true == $_SESSION['loggedin'] && + isset($_SESSION['ua']) && $_SESSION['ua'] == $_SERVER['HTTP_USER_AGENT'] + ) { + return $_SESSION['username']; + } + + return false; + } + + public function getUsername() + { + return $this->user; + } + + public function getSecret() + { + if (isset($this->data['secret'])) { + return $this->data['secret']; + } + + return false; + } + + public function generateSecret() + { + $g = new \Sonata\GoogleAuthenticator\GoogleAuthenticator(); + $secret = $g->generateSecret(); + $this->data['secret'] = $secret; + + return $secret; + } + + public function getData() + { + return $this->data; + } + + public function setOTPCookie(): void + { + $time = floor(time() / (3600 * 24)); // get day number + //about using the user agent: It's easy to fake it, but it increases the barrier for stealing and reusing cookies nevertheless + // and it doesn't do any harm (except that it's invalid after a browser upgrade, but that may be even intented) + $cookie = $time.':'.hash_hmac('sha1', $this->getUsername().':'.$time.':'.$_SERVER['HTTP_USER_AGENT'], $this->getSecret()); + setcookie('otp', $cookie, time() + (30 * 24 * 3600), null, null, null, true); + } + + public function hasValidOTPCookie() + { + // 0 = tomorrow it is invalid + $daysUntilInvalid = 0; + $time = (string) floor((time() / (3600 * 24))); // get day number + if (isset($_COOKIE['otp'])) { + list($otpday, $hash) = explode(':', $_COOKIE['otp']); + + if ($otpday >= $time - $daysUntilInvalid && $hash == hash_hmac('sha1', $this->getUsername().':'.$otpday.':'.$_SERVER['HTTP_USER_AGENT'], $this->getSecret())) { + return true; + } + } + + return false; + } +} diff --git a/vendor/sonata-project/google-authenticator/sample/web/index.php b/vendor/sonata-project/google-authenticator/sample/web/index.php new file mode 100644 index 0000000..c039674 --- /dev/null +++ b/vendor/sonata-project/google-authenticator/sample/web/index.php @@ -0,0 +1,119 @@ +<?php declare(strict_types=1); +ob_start(); //i'm too lazy to check when is sent what ;) +//set session cookie to be read only via http and not by JavaScript +ini_set('session.cookie_httponly', '1'); + +include_once __DIR__.'/../../src/GoogleAuthenticator.php'; +include_once __DIR__.'/../../src/GoogleQrUrl.php'; +include_once __DIR__.'/../../src/FixedBitNotation.php'; +include_once 'Users.php'; + +?> +<!DOCTYPE HTML> +<html> +<head> + <title>Google Authenticator in PHP demo</title> +</head> +<body> +<?php + +//set this to false, if you don't want the token prefilled +$debug = true; + +$users = new Users(); +//check if the user has a session, if not, show the login screen +if ($username = $users->hasSession()) { + //load the user data from the json storage. + $user = $users->loadUser($username); + //if he clicked logout, destroy the session and redirect to the startscreen. + if (isset($_GET['logout'])) { + session_destroy(); + header('Location: ./'); + } + // check if the user is logged in. + if ($user->isLoggedIn()) { + include __DIR__.'/../tmpl/loggedin.php'; + //show the QR code if whished so + if (isset($_GET['showqr'])) { + $secret = $user->getSecret(); + include __DIR__.'/../tmpl/show-qr.php'; + } + } + //if the user is in the OTP phase and submit the OTP. + else { + if ($user->isOTP() && isset($_POST['otp'])) { + $g = new \Google\Authenticator\GoogleAuthenticator(); + // check if the submitted token is the right one and log in + if ($g->checkCode($user->getSecret(), $_POST['otp'])) { + // do log-in the user + $user->doLogin(); + //if the user clicked the "remember the token" checkbox, set the cookie + if (isset($_POST['remember']) && $_POST['remember']) { + $user->setOTPCookie(); + } + include __DIR__.'/../tmpl/loggedin.php'; + } + //if the OTP is wrong, destroy the session and tell the user to try again + else { + session_destroy(); + include __DIR__.'/../tmpl/login-error.php'; + } + } + // if the user is neither logged in nor in the OTP phase, show the login form + else { + session_destroy(); + include __DIR__.'/../tmpl/login.php'; + } + } + die(); +} + //if the username is set in _POST, then we assume the user filled in the login form. + + if (isset($_POST['username'])) { + // check if we can load the user (ie. the user exists in our db) + $user = $users->loadUser($_POST['username']); + if ($user) { + //try to authenticate the password and start the session if it's correct. + if ($user->auth($_POST['password'])) { + $user->startSession(); + //check if the user has a valid OTP cookie, so we don't have to + // ask for the current token and can directly log in + if ($user->hasValidOTPCookie()) { + include __DIR__.'/../tmpl/loggedin.php'; + $user->doLogin(); + } + // try to get the users' secret from the db, + // if he doesn't have one, generate one, store it and show it. + else { + if (!$user->getSecret()) { + include __DIR__.'/../tmpl/loggedin.php'; + + $secret = $user->generateSecret(); + $users->storeData($user); + $user->doLogin(); + include __DIR__.'/../tmpl/show-qr.php'; + } + // if the user neither has a valid OTP cookie nor it's the first login + // ask for the OTP + else { + $user->doOTP(); + include __DIR__.'/../tmpl/ask-for-otp.php'; + } + } + + die(); + } + } + // if we're here, something went wrong, destroy the session and show a login error + session_destroy(); + + include __DIR__.'/../tmpl/login-error.php'; + die(); + } + +// if neither a session nor tried to submit the login credentials -> login screen +include __DIR__.'/../tmpl/login.php'; + +?> +</body> +</html> diff --git a/vendor/sonata-project/google-authenticator/src/FixedBitNotation.php b/vendor/sonata-project/google-authenticator/src/FixedBitNotation.php new file mode 100644 index 0000000..3f5ff55 --- /dev/null +++ b/vendor/sonata-project/google-authenticator/src/FixedBitNotation.php @@ -0,0 +1,296 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the Sonata Project package. + * + * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\GoogleAuthenticator; + +/** + * FixedBitNotation. + * + * The FixedBitNotation class is for binary to text conversion. It + * can handle many encoding schemes, formally defined or not, that + * use a fixed number of bits to encode each character. + * + * @author Andre DeMarre + */ +final class FixedBitNotation +{ + /** + * @var string + */ + private $chars; + + /** + * @var int + */ + private $bitsPerCharacter; + + /** + * @var int + */ + private $radix; + + /** + * @var bool + */ + private $rightPadFinalBits; + + /** + * @var bool + */ + private $padFinalGroup; + + /** + * @var string + */ + private $padCharacter; + + /** + * @var string[] + */ + private $charmap; + + /** + * @param int $bitsPerCharacter Bits to use for each encoded character + * @param string $chars Base character alphabet + * @param bool $rightPadFinalBits How to encode last character + * @param bool $padFinalGroup Add padding to end of encoded output + * @param string $padCharacter Character to use for padding + */ + public function __construct(int $bitsPerCharacter, string $chars = null, bool $rightPadFinalBits = false, bool $padFinalGroup = false, string $padCharacter = '=') + { + // Ensure validity of $chars + if (!is_string($chars) || ($charLength = strlen($chars)) < 2) { + $chars = + '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-,'; + $charLength = 64; + } + + // Ensure validity of $bitsPerCharacter + if ($bitsPerCharacter < 1) { + // $bitsPerCharacter must be at least 1 + $bitsPerCharacter = 1; + $radix = 2; + } elseif ($charLength < 1 << $bitsPerCharacter) { + // Character length of $chars is too small for $bitsPerCharacter + // Set $bitsPerCharacter to greatest acceptable value + $bitsPerCharacter = 1; + $radix = 2; + + while ($charLength >= ($radix <<= 1) && $bitsPerCharacter < 8) { + ++$bitsPerCharacter; + } + + $radix >>= 1; + } elseif ($bitsPerCharacter > 8) { + // $bitsPerCharacter must not be greater than 8 + $bitsPerCharacter = 8; + $radix = 256; + } else { + $radix = 1 << $bitsPerCharacter; + } + + $this->chars = $chars; + $this->bitsPerCharacter = $bitsPerCharacter; + $this->radix = $radix; + $this->rightPadFinalBits = $rightPadFinalBits; + $this->padFinalGroup = $padFinalGroup; + $this->padCharacter = $padCharacter[0]; + } + + /** + * Encode a string. + * + * @param string $rawString Binary data to encode + * + * @return string + */ + public function encode($rawString): string + { + // Unpack string into an array of bytes + $bytes = unpack('C*', $rawString); + $byteCount = count($bytes); + + $encodedString = ''; + $byte = array_shift($bytes); + $bitsRead = 0; + + $chars = $this->chars; + $bitsPerCharacter = $this->bitsPerCharacter; + $rightPadFinalBits = $this->rightPadFinalBits; + $padFinalGroup = $this->padFinalGroup; + $padCharacter = $this->padCharacter; + + // Generate encoded output; + // each loop produces one encoded character + for ($c = 0; $c < $byteCount * 8 / $bitsPerCharacter; ++$c) { + // Get the bits needed for this encoded character + if ($bitsRead + $bitsPerCharacter > 8) { + // Not enough bits remain in this byte for the current + // character + // Save the remaining bits before getting the next byte + $oldBitCount = 8 - $bitsRead; + $oldBits = $byte ^ ($byte >> $oldBitCount << $oldBitCount); + $newBitCount = $bitsPerCharacter - $oldBitCount; + + if (!$bytes) { + // Last bits; match final character and exit loop + if ($rightPadFinalBits) { + $oldBits <<= $newBitCount; + } + $encodedString .= $chars[$oldBits]; + + if ($padFinalGroup) { + // Array of the lowest common multiples of + // $bitsPerCharacter and 8, divided by 8 + $lcmMap = [1 => 1, 2 => 1, 3 => 3, 4 => 1, 5 => 5, 6 => 3, 7 => 7, 8 => 1]; + $bytesPerGroup = $lcmMap[$bitsPerCharacter]; + $pads = $bytesPerGroup * 8 / $bitsPerCharacter + - ceil((strlen($rawString) % $bytesPerGroup) + * 8 / $bitsPerCharacter); + $encodedString .= str_repeat($padCharacter[0], $pads); + } + + break; + } + + // Get next byte + $byte = array_shift($bytes); + $bitsRead = 0; + } else { + $oldBitCount = 0; + $newBitCount = $bitsPerCharacter; + } + + // Read only the needed bits from this byte + $bits = $byte >> 8 - ($bitsRead + $newBitCount); + $bits ^= $bits >> $newBitCount << $newBitCount; + $bitsRead += $newBitCount; + + if ($oldBitCount) { + // Bits come from seperate bytes, add $oldBits to $bits + $bits = ($oldBits << $newBitCount) | $bits; + } + + $encodedString .= $chars[$bits]; + } + + return $encodedString; + } + + /** + * Decode a string. + * + * @param string $encodedString Data to decode + * @param bool $caseSensitive + * @param bool $strict Returns null if $encodedString contains + * an undecodable character + * + * @return string + */ + public function decode($encodedString, $caseSensitive = true, $strict = false): string + { + if (!$encodedString || !is_string($encodedString)) { + // Empty string, nothing to decode + return ''; + } + + $chars = $this->chars; + $bitsPerCharacter = $this->bitsPerCharacter; + $radix = $this->radix; + $rightPadFinalBits = $this->rightPadFinalBits; + $padCharacter = $this->padCharacter; + + // Get index of encoded characters + if ($this->charmap) { + $charmap = $this->charmap; + } else { + $charmap = []; + + for ($i = 0; $i < $radix; ++$i) { + $charmap[$chars[$i]] = $i; + } + + $this->charmap = $charmap; + } + + // The last encoded character is $encodedString[$lastNotatedIndex] + $lastNotatedIndex = strlen($encodedString) - 1; + + // Remove trailing padding characters + while ($encodedString[$lastNotatedIndex] == $padCharacter[0]) { + $encodedString = substr($encodedString, 0, $lastNotatedIndex); + --$lastNotatedIndex; + } + + $rawString = ''; + $byte = 0; + $bitsWritten = 0; + + // Convert each encoded character to a series of unencoded bits + for ($c = 0; $c <= $lastNotatedIndex; ++$c) { + if (!isset($charmap[$encodedString[$c]]) && !$caseSensitive) { + // Encoded character was not found; try other case + if (isset($charmap[$cUpper = strtoupper($encodedString[$c])])) { + $charmap[$encodedString[$c]] = $charmap[$cUpper]; + } elseif (isset($charmap[$cLower = strtolower($encodedString[$c])])) { + $charmap[$encodedString[$c]] = $charmap[$cLower]; + } + } + + if (isset($charmap[$encodedString[$c]])) { + $bitsNeeded = 8 - $bitsWritten; + $unusedBitCount = $bitsPerCharacter - $bitsNeeded; + + // Get the new bits ready + if ($bitsNeeded > $bitsPerCharacter) { + // New bits aren't enough to complete a byte; shift them + // left into position + $newBits = $charmap[$encodedString[$c]] << $bitsNeeded + - $bitsPerCharacter; + $bitsWritten += $bitsPerCharacter; + } elseif ($c != $lastNotatedIndex || $rightPadFinalBits) { + // Zero or more too many bits to complete a byte; + // shift right + $newBits = $charmap[$encodedString[$c]] >> $unusedBitCount; + $bitsWritten = 8; //$bitsWritten += $bitsNeeded; + } else { + // Final bits don't need to be shifted + $newBits = $charmap[$encodedString[$c]]; + $bitsWritten = 8; + } + + $byte |= $newBits; + + if (8 == $bitsWritten || $c == $lastNotatedIndex) { + // Byte is ready to be written + $rawString .= pack('C', $byte); + + if ($c != $lastNotatedIndex) { + // Start the next byte + $bitsWritten = $unusedBitCount; + $byte = ($charmap[$encodedString[$c]] + ^ ($newBits << $unusedBitCount)) << 8 - $bitsWritten; + } + } + } elseif ($strict) { + // Unable to decode character; abort + return null; + } + } + + return $rawString; + } +} + +// NEXT_MAJOR: Remove class alias +class_alias('Sonata\GoogleAuthenticator\FixedBitNotation', 'Google\Authenticator\FixedBitNotation', false); diff --git a/vendor/sonata-project/google-authenticator/src/GoogleAuthenticator.php b/vendor/sonata-project/google-authenticator/src/GoogleAuthenticator.php new file mode 100644 index 0000000..145739c --- /dev/null +++ b/vendor/sonata-project/google-authenticator/src/GoogleAuthenticator.php @@ -0,0 +1,173 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the Sonata Project package. + * + * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\GoogleAuthenticator; + +/** + * @see https://github.com/google/google-authenticator/wiki/Key-Uri-Format + */ +final class GoogleAuthenticator implements GoogleAuthenticatorInterface +{ + /** + * @var int + */ + private $passCodeLength; + + /** + * @var int + */ + private $secretLength; + + /** + * @var int + */ + private $pinModulo; + + /** + * @var \DateTimeInterface + */ + private $now; + + /** + * @var int + */ + private $codePeriod = 30; + + /** + * @param int $passCodeLength + * @param int $secretLength + * @param \DateTimeInterface|null $now + */ + public function __construct(int $passCodeLength = 6, int $secretLength = 10, \DateTimeInterface $now = null) + { + $this->passCodeLength = $passCodeLength; + $this->secretLength = $secretLength; + $this->pinModulo = 10 ** $passCodeLength; + $this->now = $now ?? new \DateTimeImmutable(); + } + + /** + * @param string $secret + * @param string $code + */ + public function checkCode($secret, $code): bool + { + /** + * The result of each comparison is accumulated here instead of using a guard clause + * (https://refactoring.com/catalog/replaceNestedConditionalWithGuardClauses.html). This is to implement + * constant time comparison to make side-channel attacks harder. See + * https://cryptocoding.net/index.php/Coding_rules#Compare_secret_strings_in_constant_time for details. + * Each comparison uses hash_equals() instead of an operator to implement constant time equality comparison + * for each code. + */ + $result = 0; + + // current period + $result += hash_equals($this->getCode($secret, $this->now), $code); + + // previous period, happens if the user was slow to enter or it just crossed over + $dateTime = new \DateTimeImmutable('@'.($this->now->getTimestamp() - $this->codePeriod)); + $result += hash_equals($this->getCode($secret, $dateTime), $code); + + // next period, happens if the user is not completely synced and possibly a few seconds ahead + $dateTime = new \DateTimeImmutable('@'.($this->now->getTimestamp() + $this->codePeriod)); + $result += hash_equals($this->getCode($secret, $dateTime), $code); + + return $result > 0; + } + + /** + * NEXT_MAJOR: add the interface typehint to $time and remove deprecation. + * + * @param string $secret + * @param float|string|int|null|\DateTimeInterface $time + */ + public function getCode($secret, /* \DateTimeInterface */$time = null): string + { + if (null === $time) { + $time = $this->now; + } + + if ($time instanceof \DateTimeInterface) { + $timeForCode = floor($time->getTimestamp() / $this->codePeriod); + } else { + @trigger_error( + 'Passing anything other than null or a DateTimeInterface to $time is deprecated as of 2.0 '. + 'and will not be possible as of 3.0.', + E_USER_DEPRECATED + ); + $timeForCode = $time; + } + + $base32 = new FixedBitNotation(5, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', true, true); + $secret = $base32->decode($secret); + + $timeForCode = str_pad(pack('N', $timeForCode), 8, chr(0), STR_PAD_LEFT); + + $hash = hash_hmac('sha1', $timeForCode, $secret, true); + $offset = ord(substr($hash, -1)); + $offset &= 0xF; + + $truncatedHash = $this->hashToInt($hash, $offset) & 0x7FFFFFFF; + + return str_pad((string) ($truncatedHash % $this->pinModulo), $this->passCodeLength, '0', STR_PAD_LEFT); + } + + /** + * NEXT_MAJOR: Remove this method. + * + * @param string $user + * @param string $hostname + * @param string $secret + * + * @deprecated deprecated as of 2.1 and will be removed in 3.0. Use Sonata\GoogleAuthenticator\GoogleQrUrl::generate() instead. + */ + public function getUrl($user, $hostname, $secret): string + { + @trigger_error(sprintf( + 'Using %s() is deprecated as of 2.1 and will be removed in 3.0. '. + 'Use Sonata\GoogleAuthenticator\GoogleQrUrl::generate() instead.', + __METHOD__ + ), E_USER_DEPRECATED); + + $issuer = func_get_args()[3] ?? null; + $accountName = sprintf('%s@%s', $user, $hostname); + + // manually concat the issuer to avoid a change in URL + $url = GoogleQrUrl::generate($accountName, $secret); + + if ($issuer) { + $url .= '%26issuer%3D'.$issuer; + } + + return $url; + } + + public function generateSecret(): string + { + return (new FixedBitNotation(5, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', true, true)) + ->encode(random_bytes($this->secretLength)); + } + + /** + * @param string $bytes + * @param int $start + */ + private function hashToInt(string $bytes, int $start): int + { + return unpack('N', substr(substr($bytes, $start), 0, 4))[1]; + } +} + +// NEXT_MAJOR: Remove class alias +class_alias('Sonata\GoogleAuthenticator\GoogleAuthenticator', 'Google\Authenticator\GoogleAuthenticator', false); diff --git a/vendor/sonata-project/google-authenticator/src/GoogleAuthenticatorInterface.php b/vendor/sonata-project/google-authenticator/src/GoogleAuthenticatorInterface.php new file mode 100644 index 0000000..70219f2 --- /dev/null +++ b/vendor/sonata-project/google-authenticator/src/GoogleAuthenticatorInterface.php @@ -0,0 +1,44 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the Sonata Project package. + * + * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\GoogleAuthenticator; + +interface GoogleAuthenticatorInterface +{ + /** + * @param string $secret + * @param string $code + */ + public function checkCode($secret, $code): bool; + + /** + * NEXT_MAJOR: add the interface typehint to $time and remove deprecation. + * + * @param string $secret + * @param float|string|int|null|\DateTimeInterface $time + */ + public function getCode($secret, /* \DateTimeInterface */$time = null): string; + + /** + * NEXT_MAJOR: Remove this method. + * + * @param string $user + * @param string $hostname + * @param string $secret + * + * @deprecated deprecated as of 2.1 and will be removed in 3.0. Use Sonata\GoogleAuthenticator\GoogleQrUrl::generate() instead. + */ + public function getUrl($user, $hostname, $secret): string; + + public function generateSecret(): string; +} diff --git a/vendor/sonata-project/google-authenticator/src/GoogleQrUrl.php b/vendor/sonata-project/google-authenticator/src/GoogleQrUrl.php new file mode 100644 index 0000000..7e9a537 --- /dev/null +++ b/vendor/sonata-project/google-authenticator/src/GoogleQrUrl.php @@ -0,0 +1,94 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the Sonata Project package. + * + * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\GoogleAuthenticator; + +/** + * Responsible for QR image url generation. + * + * @see https://developers.google.com/chart/infographics/docs/qr_codes + * @see https://github.com/google/google-authenticator/wiki/Key-Uri-Format + * + * @author Iltar van der Berg <kjarli@gmail.com> + */ +final class GoogleQrUrl +{ + /** + * Private by design. + */ + private function __construct() + { + } + + /** + * Generates a URL that is used to show a QR code. + * + * Account names may not contain a double colon (:). Valid account name + * examples: + * - "John.Doe@gmail.com" + * - "John Doe" + * - "John_Doe_976" + * + * The Issuer may not contain a double colon (:). The issuer is recommended + * to pass along. If used, it will also be appended before the accountName. + * + * The previous examples with the issuer "Acme inc" would result in label: + * - "Acme inc:John.Doe@gmail.com" + * - "Acme inc:John Doe" + * - "Acme inc:John_Doe_976" + * + * The contents of the label, issuer and secret will be encoded to generate + * a valid URL. + * + * @param string $accountName The account name to show and identify + * @param string $secret The secret is the generated secret unique to that user + * @param string|null $issuer Where you log in to + * @param int $size Image size in pixels, 200 will make it 200x200 + * + * @return string + */ + public static function generate(string $accountName, string $secret, string $issuer = null, int $size = 200): string + { + if ('' === $accountName || false !== strpos($accountName, ':')) { + throw RuntimeException::InvalidAccountName($accountName); + } + + if ('' === $secret) { + throw RuntimeException::InvalidSecret(); + } + + $label = $accountName; + $otpauthString = 'otpauth://totp/%s?secret=%s'; + + if (null !== $issuer) { + if ('' === $issuer || false !== strpos($issuer, ':')) { + throw RuntimeException::InvalidIssuer($issuer); + } + + // use both the issuer parameter and label prefix as recommended by Google for BC reasons + $label = $issuer.':'.$label; + $otpauthString .= '&issuer=%s'; + } + + $otpauthString = rawurlencode(sprintf($otpauthString, $label, $secret, $issuer)); + + return sprintf( + 'https://chart.googleapis.com/chart?chs=%1$dx%1$d&chld=M|0&cht=qr&chl=%2$s', + $size, + $otpauthString + ); + } +} + +// NEXT_MAJOR: Remove class alias +class_alias('Sonata\GoogleAuthenticator\GoogleQrUrl', 'Google\Authenticator\GoogleQrUrl', false); diff --git a/vendor/sonata-project/google-authenticator/src/RuntimeException.php b/vendor/sonata-project/google-authenticator/src/RuntimeException.php new file mode 100644 index 0000000..4d3cf5f --- /dev/null +++ b/vendor/sonata-project/google-authenticator/src/RuntimeException.php @@ -0,0 +1,46 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the Sonata Project package. + * + * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\GoogleAuthenticator; + +/** + * Contains runtime exception templates. + * + * @author Iltar van der Berg <kjarli@gmail.com> + */ +final class RuntimeException extends \RuntimeException +{ + public static function InvalidAccountName(string $accountName): self + { + return new self(sprintf( + 'The account name may not contain a double colon (:) and may not be an empty string. Given "%s".', + $accountName + )); + } + + public static function InvalidIssuer(string $issuer): self + { + return new self(sprintf( + 'The issuer name may not contain a double colon (:) and may not be an empty string. Given "%s".', + $issuer + )); + } + + public static function InvalidSecret(): self + { + return new self('The secret name may not be an empty string.'); + } +} + +// NEXT_MAJOR: Remove class alias +class_alias('Sonata\GoogleAuthenticator\RuntimeException', 'Google\Authenticator\RuntimeException', false); -- GitLab