From d2dfb7071a5b85c3f1531220db6ef2d12d8390bf Mon Sep 17 00:00:00 2001 From: jean Date: Wed, 20 Mar 2024 22:43:36 -0500 Subject: [PATCH 1/7] =?UTF-8?q?Creaci=C3=B3n=20modulo=20autenticaci=C3=B3n?= =?UTF-8?q?=20con=20JWT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/login.php | 79 +++ api/protected.php | 56 ++ api/register.php | 59 ++ composer.json | 5 + composer.lock | 82 +++ config/database.php | 24 + index.php | 0 vendor/autoload.php | 25 + vendor/composer/ClassLoader.php | 579 +++++++++++++++ vendor/composer/InstalledVersions.php | 359 ++++++++++ vendor/composer/LICENSE | 21 + vendor/composer/autoload_classmap.php | 10 + vendor/composer/autoload_namespaces.php | 9 + vendor/composer/autoload_psr4.php | 10 + vendor/composer/autoload_real.php | 38 + vendor/composer/autoload_static.php | 36 + vendor/composer/installed.json | 72 ++ vendor/composer/installed.php | 32 + vendor/composer/platform_check.php | 26 + vendor/firebase/php-jwt/CHANGELOG.md | 170 +++++ vendor/firebase/php-jwt/LICENSE | 30 + vendor/firebase/php-jwt/README.md | 424 +++++++++++ vendor/firebase/php-jwt/composer.json | 42 ++ .../php-jwt/src/BeforeValidException.php | 18 + vendor/firebase/php-jwt/src/CachedKeySet.php | 268 +++++++ .../firebase/php-jwt/src/ExpiredException.php | 18 + vendor/firebase/php-jwt/src/JWK.php | 349 +++++++++ vendor/firebase/php-jwt/src/JWT.php | 669 ++++++++++++++++++ .../src/JWTExceptionWithPayloadInterface.php | 20 + vendor/firebase/php-jwt/src/Key.php | 64 ++ .../php-jwt/src/SignatureInvalidException.php | 7 + 31 files changed, 3601 insertions(+) create mode 100644 api/login.php create mode 100644 api/protected.php create mode 100644 api/register.php create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 config/database.php create mode 100644 index.php create mode 100644 vendor/autoload.php create mode 100644 vendor/composer/ClassLoader.php create mode 100644 vendor/composer/InstalledVersions.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/composer/installed.php create mode 100644 vendor/composer/platform_check.php create mode 100644 vendor/firebase/php-jwt/CHANGELOG.md create mode 100644 vendor/firebase/php-jwt/LICENSE create mode 100644 vendor/firebase/php-jwt/README.md create mode 100644 vendor/firebase/php-jwt/composer.json create mode 100644 vendor/firebase/php-jwt/src/BeforeValidException.php create mode 100644 vendor/firebase/php-jwt/src/CachedKeySet.php create mode 100644 vendor/firebase/php-jwt/src/ExpiredException.php create mode 100644 vendor/firebase/php-jwt/src/JWK.php create mode 100644 vendor/firebase/php-jwt/src/JWT.php create mode 100644 vendor/firebase/php-jwt/src/JWTExceptionWithPayloadInterface.php create mode 100644 vendor/firebase/php-jwt/src/Key.php create mode 100644 vendor/firebase/php-jwt/src/SignatureInvalidException.php diff --git a/api/login.php b/api/login.php new file mode 100644 index 0000000..8ffb6e7 --- /dev/null +++ b/api/login.php @@ -0,0 +1,79 @@ +getConnection(); + +$data = json_decode(file_get_contents("php://input")); + +$email_user = $data->email_user; +$passw_user = $data->passw_user; + +$table_name = 'users'; + +$query = "SELECT id_user, nomb_user, apell_user, passw_user FROM " . $table_name . " WHERE email_user = ? LIMIT 0,1"; + +$stmt = $conn->prepare( $query ); +$stmt->bindParam(1, $email_user); +$stmt->execute(); +$num = $stmt->rowCount(); + +if($num > 0){ + $row = $stmt->fetch(PDO::FETCH_ASSOC); + $id_user = $row['id_user']; + $nomb_user = $row['nomb_user']; + $apell_user = $row['apell_user']; + $passw_db = $row['passw_user']; + + if(password_verify($passw_user, $passw_db)) + { + $secret_key = "123api"; + $issuer_claim = "localhost"; // this can be the servername + $audience_claim = "THE_AUDIENCE"; + $issuedat_claim = time(); // issued at + $notbefore_claim = $issuedat_claim + 1; //not before in seconds + $expire_claim = $issuedat_claim + 90000; // expire time in seconds + // $now = strtotime("now"); + $token = array( + "iss" => $issuer_claim, + "aud" => $audience_claim, + "iat" => $issuedat_claim, + "nbf" => $notbefore_claim, + "exp" => $expire_claim, + "data" => array( + "id_user" => $id_user, + "nomb_user" => $nomb_user, + "apell_user" => $apell_user, + "email_user" => $email_user + )); + + http_response_code(200); + + $jwt = JWT::encode($token, $secret_key, 'HS256'); + echo json_encode( + array( + "message" => "Inicio de sesión satisfactorio.", + "jwt" => $jwt, + "email" => $email_user, + "expireAt" => $expire_claim + )); + } + else{ + + http_response_code(401); + echo json_encode(array("message" => "Inicio de sesión fallido.")); + } +} +?> \ No newline at end of file diff --git a/api/protected.php b/api/protected.php new file mode 100644 index 0000000..7ca9672 --- /dev/null +++ b/api/protected.php @@ -0,0 +1,56 @@ +getConnection(); + +$data = json_decode(file_get_contents("php://input")); + + +$authHeader = $_SERVER['HTTP_AUTHORIZATION']; + +$arr = explode(" ", $authHeader); + + +echo json_encode(array( + "message" => "sd" .$arr[1] +)); + +$jwt = $arr[1]; + +if($jwt){ + + try { + + $decoded = JWT::decode($jwt, $secret_key, array('HS256')); + + // Access is granted. Add code of the operation here + + echo json_encode(array( + "message" => "Access granted:", + "error" => $e->getMessage() + )); + + }catch (Exception $e){ + + http_response_code(401); + + echo json_encode(array( + "message" => "Access denied.", + "error" => $e->getMessage() + )); +} + +} +?> \ No newline at end of file diff --git a/api/register.php b/api/register.php new file mode 100644 index 0000000..1ad4c22 --- /dev/null +++ b/api/register.php @@ -0,0 +1,59 @@ +getConnection(); + +$data = json_decode(file_get_contents("php://input")); + +$nomb_user = $data->nomb_user; +$apell_user = $data->apell_user; +$nick_user = $data->nick_user; +$email_user = $data->email_user; +$password = $data->password; + +$table_name = 'users'; + +$query = "INSERT INTO " . $table_name . " + SET nomb_user = :nomb_user, + apell_user = :apell_user, + nick_user = :nick_user, + email_user = :email_user, + passw_user = :password"; + +$stmt = $conn->prepare($query); + +$stmt->bindParam(':nomb_user', $nomb_user); +$stmt->bindParam(':apell_user', $apell_user); +$stmt->bindParam(':nick_user', $nick_user); +$stmt->bindParam(':email_user', $email_user); + +$password_hash = password_hash($password, PASSWORD_BCRYPT); + +$stmt->bindParam(':password', $password_hash); + +if($stmt->execute()){ + + http_response_code(200); + echo json_encode(array("message" => "Usuario registrado correctamente.")); +} +else{ + http_response_code(400); + + echo json_encode(array("message" => "Error al registrar usuario.")); +} +?> + diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..cace316 --- /dev/null +++ b/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "firebase/php-jwt": "^6.10" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..9093509 --- /dev/null +++ b/composer.lock @@ -0,0 +1,82 @@ +{ + "_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": "dfe27548af6d83b7d428280eae1df087", + "packages": [ + { + "name": "firebase/php-jwt", + "version": "v6.10.0", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "a49db6f0a5033aef5143295342f1c95521b075ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/a49db6f0a5033aef5143295342f1c95521b075ff", + "reference": "a49db6f0a5033aef5143295342f1c95521b075ff", + "shasum": "" + }, + "require": { + "php": "^7.4||^8.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^6.5||^7.4", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psr/cache": "^1.0||^2.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + }, + "suggest": { + "ext-sodium": "Support EdDSA (Ed25519) signatures", + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": [ + "jwt", + "php" + ], + "support": { + "issues": "https://github.com/firebase/php-jwt/issues", + "source": "https://github.com/firebase/php-jwt/tree/v6.10.0" + }, + "time": "2023-12-01T16:26:39+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/config/database.php b/config/database.php new file mode 100644 index 0000000..e5954ab --- /dev/null +++ b/config/database.php @@ -0,0 +1,24 @@ +connection = null; + + try{ + $this->connection = new PDO("mysql:host=" . $this->db_host . ";dbname=" . $this->db_name, $this->db_user, $this->db_password); + }catch(PDOException $exception){ + echo "Connection failed: " . $exception->getMessage(); + } + + return $this->connection; + } +} +?> \ No newline at end of file diff --git a/index.php b/index.php new file mode 100644 index 0000000..e69de29 diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 0000000..a8f9cfc --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,25 @@ + + * Jordi Boggiano + * + * 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 + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var string|null */ + private $vendorDir; + + // PSR-4 + /** + * @var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var list + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * List of PSR-0 prefixes + * + * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) + * + * @var array>> + */ + private $prefixesPsr0 = array(); + /** + * @var list + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var array + */ + private $missingClasses = array(); + + /** @var string|null */ + private $apcuPrefix; + + /** + * @var array + */ + private static $registeredLoaders = array(); + + /** + * @param string|null $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); + } + + /** + * @return array> + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return list + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return list + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return array Array of classname => path + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + * + * @return void + */ + 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 list|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + $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 list|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + $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] = $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + $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 list|string $paths The PSR-0 base directories + * + * @return void + */ + 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 list|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + 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 + * + * @return void + */ + 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 + * + * @return void + */ + 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 + * + * @return void + */ + 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 + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + $includeFile = self::$includeFile; + $includeFile($file); + + return true; + } + + return null; + } + + /** + * 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; + } + + /** + * Returns the currently registered loaders keyed by their corresponding vendor directories. + * + * @return array + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + 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; + } + + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } +} diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000..51e734a --- /dev/null +++ b/vendor/composer/InstalledVersions.php @@ -0,0 +1,359 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints((string) $constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + + if (self::$canGetVendors) { + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require $vendorDir.'/composer/installed.php'; + $installed[] = self::$installedByVendor[$vendorDir] = $required; + if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + self::$installed = $installed[count($installed) - 1]; + } + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; + } else { + self::$installed = array(); + } + } + + if (self::$installed !== array()) { + $installed[] = self::$installed; + } + + return $installed; + } +} 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..0fb0a2c --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,10 @@ + $vendorDir . '/composer/InstalledVersions.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..15a2ff3 --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($vendorDir . '/firebase/php-jwt/src'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000..936cee1 --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,38 @@ +register(true); + + return $loader; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 0000000..db0c39b --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,36 @@ + + array ( + 'Firebase\\JWT\\' => 13, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Firebase\\JWT\\' => + array ( + 0 => __DIR__ . '/..' . '/firebase/php-jwt/src', + ), + ); + + public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit20fad51902f91e7fd3039e016a6556b5::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit20fad51902f91e7fd3039e016a6556b5::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInit20fad51902f91e7fd3039e016a6556b5::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 0000000..c92f3e4 --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,72 @@ +{ + "packages": [ + { + "name": "firebase/php-jwt", + "version": "v6.10.0", + "version_normalized": "6.10.0.0", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "a49db6f0a5033aef5143295342f1c95521b075ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/a49db6f0a5033aef5143295342f1c95521b075ff", + "reference": "a49db6f0a5033aef5143295342f1c95521b075ff", + "shasum": "" + }, + "require": { + "php": "^7.4||^8.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^6.5||^7.4", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psr/cache": "^1.0||^2.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + }, + "suggest": { + "ext-sodium": "Support EdDSA (Ed25519) signatures", + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + }, + "time": "2023-12-01T16:26:39+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": [ + "jwt", + "php" + ], + "support": { + "issues": "https://github.com/firebase/php-jwt/issues", + "source": "https://github.com/firebase/php-jwt/tree/v6.10.0" + }, + "install-path": "../firebase/php-jwt" + } + ], + "dev": true, + "dev-package-names": [] +} diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php new file mode 100644 index 0000000..0892c1f --- /dev/null +++ b/vendor/composer/installed.php @@ -0,0 +1,32 @@ + array( + 'name' => '__root__', + 'pretty_version' => '1.0.0+no-version-set', + 'version' => '1.0.0.0', + 'reference' => null, + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => true, + ), + 'versions' => array( + '__root__' => array( + 'pretty_version' => '1.0.0+no-version-set', + 'version' => '1.0.0.0', + 'reference' => null, + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'firebase/php-jwt' => array( + 'pretty_version' => 'v6.10.0', + 'version' => '6.10.0.0', + 'reference' => 'a49db6f0a5033aef5143295342f1c95521b075ff', + 'type' => 'library', + 'install_path' => __DIR__ . '/../firebase/php-jwt', + 'aliases' => array(), + 'dev_requirement' => false, + ), + ), +); diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php new file mode 100644 index 0000000..580fa96 --- /dev/null +++ b/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ += 70400)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 7.4.0". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR + ); +} diff --git a/vendor/firebase/php-jwt/CHANGELOG.md b/vendor/firebase/php-jwt/CHANGELOG.md new file mode 100644 index 0000000..644fa0b --- /dev/null +++ b/vendor/firebase/php-jwt/CHANGELOG.md @@ -0,0 +1,170 @@ +# Changelog + +## [6.10.0](https://github.com/firebase/php-jwt/compare/v6.9.0...v6.10.0) (2023-11-28) + + +### Features + +* allow typ header override ([#546](https://github.com/firebase/php-jwt/issues/546)) ([79cb30b](https://github.com/firebase/php-jwt/commit/79cb30b729a22931b2fbd6b53f20629a83031ba9)) + +## [6.9.0](https://github.com/firebase/php-jwt/compare/v6.8.1...v6.9.0) (2023-10-04) + + +### Features + +* add payload to jwt exception ([#521](https://github.com/firebase/php-jwt/issues/521)) ([175edf9](https://github.com/firebase/php-jwt/commit/175edf958bb61922ec135b2333acf5622f2238a2)) + +## [6.8.1](https://github.com/firebase/php-jwt/compare/v6.8.0...v6.8.1) (2023-07-14) + + +### Bug Fixes + +* accept float claims but round down to ignore them ([#492](https://github.com/firebase/php-jwt/issues/492)) ([3936842](https://github.com/firebase/php-jwt/commit/39368423beeaacb3002afa7dcb75baebf204fe7e)) +* different BeforeValidException messages for nbf and iat ([#526](https://github.com/firebase/php-jwt/issues/526)) ([0a53cf2](https://github.com/firebase/php-jwt/commit/0a53cf2986e45c2bcbf1a269f313ebf56a154ee4)) + +## [6.8.0](https://github.com/firebase/php-jwt/compare/v6.7.0...v6.8.0) (2023-06-14) + + +### Features + +* add support for P-384 curve ([#515](https://github.com/firebase/php-jwt/issues/515)) ([5de4323](https://github.com/firebase/php-jwt/commit/5de4323f4baf4d70bca8663bd87682a69c656c3d)) + + +### Bug Fixes + +* handle invalid http responses ([#508](https://github.com/firebase/php-jwt/issues/508)) ([91c39c7](https://github.com/firebase/php-jwt/commit/91c39c72b22fc3e1191e574089552c1f2041c718)) + +## [6.7.0](https://github.com/firebase/php-jwt/compare/v6.6.0...v6.7.0) (2023-06-14) + + +### Features + +* add ed25519 support to JWK (public keys) ([#452](https://github.com/firebase/php-jwt/issues/452)) ([e53979a](https://github.com/firebase/php-jwt/commit/e53979abae927de916a75b9d239cfda8ce32be2a)) + +## [6.6.0](https://github.com/firebase/php-jwt/compare/v6.5.0...v6.6.0) (2023-06-13) + + +### Features + +* allow get headers when decoding token ([#442](https://github.com/firebase/php-jwt/issues/442)) ([fb85f47](https://github.com/firebase/php-jwt/commit/fb85f47cfaeffdd94faf8defdf07164abcdad6c3)) + + +### Bug Fixes + +* only check iat if nbf is not used ([#493](https://github.com/firebase/php-jwt/issues/493)) ([398ccd2](https://github.com/firebase/php-jwt/commit/398ccd25ea12fa84b9e4f1085d5ff448c21ec797)) + +## [6.5.0](https://github.com/firebase/php-jwt/compare/v6.4.0...v6.5.0) (2023-05-12) + + +### Bug Fixes + +* allow KID of '0' ([#505](https://github.com/firebase/php-jwt/issues/505)) ([9dc46a9](https://github.com/firebase/php-jwt/commit/9dc46a9c3e5801294249cfd2554c5363c9f9326a)) + + +### Miscellaneous Chores + +* drop support for PHP 7.3 ([#495](https://github.com/firebase/php-jwt/issues/495)) + +## [6.4.0](https://github.com/firebase/php-jwt/compare/v6.3.2...v6.4.0) (2023-02-08) + + +### Features + +* add support for W3C ES256K ([#462](https://github.com/firebase/php-jwt/issues/462)) ([213924f](https://github.com/firebase/php-jwt/commit/213924f51936291fbbca99158b11bd4ae56c2c95)) +* improve caching by only decoding jwks when necessary ([#486](https://github.com/firebase/php-jwt/issues/486)) ([78d3ed1](https://github.com/firebase/php-jwt/commit/78d3ed1073553f7d0bbffa6c2010009a0d483d5c)) + +## [6.3.2](https://github.com/firebase/php-jwt/compare/v6.3.1...v6.3.2) (2022-11-01) + + +### Bug Fixes + +* check kid before using as array index ([bad1b04](https://github.com/firebase/php-jwt/commit/bad1b040d0c736bbf86814c6b5ae614f517cf7bd)) + +## [6.3.1](https://github.com/firebase/php-jwt/compare/v6.3.0...v6.3.1) (2022-11-01) + + +### Bug Fixes + +* casing of GET for PSR compat ([#451](https://github.com/firebase/php-jwt/issues/451)) ([60b52b7](https://github.com/firebase/php-jwt/commit/60b52b71978790eafcf3b95cfbd83db0439e8d22)) +* string interpolation format for php 8.2 ([#446](https://github.com/firebase/php-jwt/issues/446)) ([2e07d8a](https://github.com/firebase/php-jwt/commit/2e07d8a1524d12b69b110ad649f17461d068b8f2)) + +## 6.3.0 / 2022-07-15 + + - Added ES256 support to JWK parsing ([#399](https://github.com/firebase/php-jwt/pull/399)) + - Fixed potential caching error in `CachedKeySet` by caching jwks as strings ([#435](https://github.com/firebase/php-jwt/pull/435)) + +## 6.2.0 / 2022-05-14 + + - Added `CachedKeySet` ([#397](https://github.com/firebase/php-jwt/pull/397)) + - Added `$defaultAlg` parameter to `JWT::parseKey` and `JWT::parseKeySet` ([#426](https://github.com/firebase/php-jwt/pull/426)). + +## 6.1.0 / 2022-03-23 + + - Drop support for PHP 5.3, 5.4, 5.5, 5.6, and 7.0 + - Add parameter typing and return types where possible + +## 6.0.0 / 2022-01-24 + + - **Backwards-Compatibility Breaking Changes**: See the [Release Notes](https://github.com/firebase/php-jwt/releases/tag/v6.0.0) for more information. + - New Key object to prevent key/algorithm type confusion (#365) + - Add JWK support (#273) + - Add ES256 support (#256) + - Add ES384 support (#324) + - Add Ed25519 support (#343) + +## 5.0.0 / 2017-06-26 +- Support RS384 and RS512. + See [#117](https://github.com/firebase/php-jwt/pull/117). Thanks [@joostfaassen](https://github.com/joostfaassen)! +- Add an example for RS256 openssl. + See [#125](https://github.com/firebase/php-jwt/pull/125). Thanks [@akeeman](https://github.com/akeeman)! +- Detect invalid Base64 encoding in signature. + See [#162](https://github.com/firebase/php-jwt/pull/162). Thanks [@psignoret](https://github.com/psignoret)! +- Update `JWT::verify` to handle OpenSSL errors. + See [#159](https://github.com/firebase/php-jwt/pull/159). Thanks [@bshaffer](https://github.com/bshaffer)! +- Add `array` type hinting to `decode` method + See [#101](https://github.com/firebase/php-jwt/pull/101). Thanks [@hywak](https://github.com/hywak)! +- Add all JSON error types. + See [#110](https://github.com/firebase/php-jwt/pull/110). Thanks [@gbalduzzi](https://github.com/gbalduzzi)! +- Bugfix 'kid' not in given key list. + See [#129](https://github.com/firebase/php-jwt/pull/129). Thanks [@stampycode](https://github.com/stampycode)! +- Miscellaneous cleanup, documentation and test fixes. + See [#107](https://github.com/firebase/php-jwt/pull/107), [#115](https://github.com/firebase/php-jwt/pull/115), + [#160](https://github.com/firebase/php-jwt/pull/160), [#161](https://github.com/firebase/php-jwt/pull/161), and + [#165](https://github.com/firebase/php-jwt/pull/165). Thanks [@akeeman](https://github.com/akeeman), + [@chinedufn](https://github.com/chinedufn), and [@bshaffer](https://github.com/bshaffer)! + +## 4.0.0 / 2016-07-17 +- Add support for late static binding. See [#88](https://github.com/firebase/php-jwt/pull/88) for details. Thanks to [@chappy84](https://github.com/chappy84)! +- Use static `$timestamp` instead of `time()` to improve unit testing. See [#93](https://github.com/firebase/php-jwt/pull/93) for details. Thanks to [@josephmcdermott](https://github.com/josephmcdermott)! +- Fixes to exceptions classes. See [#81](https://github.com/firebase/php-jwt/pull/81) for details. Thanks to [@Maks3w](https://github.com/Maks3w)! +- Fixes to PHPDoc. See [#76](https://github.com/firebase/php-jwt/pull/76) for details. Thanks to [@akeeman](https://github.com/akeeman)! + +## 3.0.0 / 2015-07-22 +- Minimum PHP version updated from `5.2.0` to `5.3.0`. +- Add `\Firebase\JWT` namespace. See +[#59](https://github.com/firebase/php-jwt/pull/59) for details. Thanks to +[@Dashron](https://github.com/Dashron)! +- Require a non-empty key to decode and verify a JWT. See +[#60](https://github.com/firebase/php-jwt/pull/60) for details. Thanks to +[@sjones608](https://github.com/sjones608)! +- Cleaner documentation blocks in the code. See +[#62](https://github.com/firebase/php-jwt/pull/62) for details. Thanks to +[@johanderuijter](https://github.com/johanderuijter)! + +## 2.2.0 / 2015-06-22 +- Add support for adding custom, optional JWT headers to `JWT::encode()`. See +[#53](https://github.com/firebase/php-jwt/pull/53/files) for details. Thanks to +[@mcocaro](https://github.com/mcocaro)! + +## 2.1.0 / 2015-05-20 +- Add support for adding a leeway to `JWT:decode()` that accounts for clock skew +between signing and verifying entities. Thanks to [@lcabral](https://github.com/lcabral)! +- Add support for passing an object implementing the `ArrayAccess` interface for +`$keys` argument in `JWT::decode()`. Thanks to [@aztech-dev](https://github.com/aztech-dev)! + +## 2.0.0 / 2015-04-01 +- **Note**: It is strongly recommended that you update to > v2.0.0 to address + known security vulnerabilities in prior versions when both symmetric and + asymmetric keys are used together. +- Update signature for `JWT::decode(...)` to require an array of supported + algorithms to use when verifying token signatures. diff --git a/vendor/firebase/php-jwt/LICENSE b/vendor/firebase/php-jwt/LICENSE new file mode 100644 index 0000000..11c0146 --- /dev/null +++ b/vendor/firebase/php-jwt/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2011, Neuman Vong + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of the copyright holder nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/firebase/php-jwt/README.md b/vendor/firebase/php-jwt/README.md new file mode 100644 index 0000000..701de23 --- /dev/null +++ b/vendor/firebase/php-jwt/README.md @@ -0,0 +1,424 @@ +![Build Status](https://github.com/firebase/php-jwt/actions/workflows/tests.yml/badge.svg) +[![Latest Stable Version](https://poser.pugx.org/firebase/php-jwt/v/stable)](https://packagist.org/packages/firebase/php-jwt) +[![Total Downloads](https://poser.pugx.org/firebase/php-jwt/downloads)](https://packagist.org/packages/firebase/php-jwt) +[![License](https://poser.pugx.org/firebase/php-jwt/license)](https://packagist.org/packages/firebase/php-jwt) + +PHP-JWT +======= +A simple library to encode and decode JSON Web Tokens (JWT) in PHP, conforming to [RFC 7519](https://tools.ietf.org/html/rfc7519). + +Installation +------------ + +Use composer to manage your dependencies and download PHP-JWT: + +```bash +composer require firebase/php-jwt +``` + +Optionally, install the `paragonie/sodium_compat` package from composer if your +php is < 7.2 or does not have libsodium installed: + +```bash +composer require paragonie/sodium_compat +``` + +Example +------- +```php +use Firebase\JWT\JWT; +use Firebase\JWT\Key; + +$key = 'example_key'; +$payload = [ + 'iss' => 'http://example.org', + 'aud' => 'http://example.com', + 'iat' => 1356999524, + 'nbf' => 1357000000 +]; + +/** + * IMPORTANT: + * You must specify supported algorithms for your application. See + * https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40 + * for a list of spec-compliant algorithms. + */ +$jwt = JWT::encode($payload, $key, 'HS256'); +$decoded = JWT::decode($jwt, new Key($key, 'HS256')); +print_r($decoded); + +// Pass a stdClass in as the third parameter to get the decoded header values +$decoded = JWT::decode($jwt, new Key($key, 'HS256'), $headers = new stdClass()); +print_r($headers); + +/* + NOTE: This will now be an object instead of an associative array. To get + an associative array, you will need to cast it as such: +*/ + +$decoded_array = (array) $decoded; + +/** + * You can add a leeway to account for when there is a clock skew times between + * the signing and verifying servers. It is recommended that this leeway should + * not be bigger than a few minutes. + * + * Source: http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#nbfDef + */ +JWT::$leeway = 60; // $leeway in seconds +$decoded = JWT::decode($jwt, new Key($key, 'HS256')); +``` +Example encode/decode headers +------- +Decoding the JWT headers without verifying the JWT first is NOT recommended, and is not supported by +this library. This is because without verifying the JWT, the header values could have been tampered with. +Any value pulled from an unverified header should be treated as if it could be any string sent in from an +attacker. If this is something you still want to do in your application for whatever reason, it's possible to +decode the header values manually simply by calling `json_decode` and `base64_decode` on the JWT +header part: +```php +use Firebase\JWT\JWT; + +$key = 'example_key'; +$payload = [ + 'iss' => 'http://example.org', + 'aud' => 'http://example.com', + 'iat' => 1356999524, + 'nbf' => 1357000000 +]; + +$headers = [ + 'x-forwarded-for' => 'www.google.com' +]; + +// Encode headers in the JWT string +$jwt = JWT::encode($payload, $key, 'HS256', null, $headers); + +// Decode headers from the JWT string WITHOUT validation +// **IMPORTANT**: This operation is vulnerable to attacks, as the JWT has not yet been verified. +// These headers could be any value sent by an attacker. +list($headersB64, $payloadB64, $sig) = explode('.', $jwt); +$decoded = json_decode(base64_decode($headersB64), true); + +print_r($decoded); +``` +Example with RS256 (openssl) +---------------------------- +```php +use Firebase\JWT\JWT; +use Firebase\JWT\Key; + +$privateKey = << 'example.org', + 'aud' => 'example.com', + 'iat' => 1356999524, + 'nbf' => 1357000000 +]; + +$jwt = JWT::encode($payload, $privateKey, 'RS256'); +echo "Encode:\n" . print_r($jwt, true) . "\n"; + +$decoded = JWT::decode($jwt, new Key($publicKey, 'RS256')); + +/* + NOTE: This will now be an object instead of an associative array. To get + an associative array, you will need to cast it as such: +*/ + +$decoded_array = (array) $decoded; +echo "Decode:\n" . print_r($decoded_array, true) . "\n"; +``` + +Example with a passphrase +------------------------- + +```php +use Firebase\JWT\JWT; +use Firebase\JWT\Key; + +// Your passphrase +$passphrase = '[YOUR_PASSPHRASE]'; + +// Your private key file with passphrase +// Can be generated with "ssh-keygen -t rsa -m pem" +$privateKeyFile = '/path/to/key-with-passphrase.pem'; + +// Create a private key of type "resource" +$privateKey = openssl_pkey_get_private( + file_get_contents($privateKeyFile), + $passphrase +); + +$payload = [ + 'iss' => 'example.org', + 'aud' => 'example.com', + 'iat' => 1356999524, + 'nbf' => 1357000000 +]; + +$jwt = JWT::encode($payload, $privateKey, 'RS256'); +echo "Encode:\n" . print_r($jwt, true) . "\n"; + +// Get public key from the private key, or pull from from a file. +$publicKey = openssl_pkey_get_details($privateKey)['key']; + +$decoded = JWT::decode($jwt, new Key($publicKey, 'RS256')); +echo "Decode:\n" . print_r((array) $decoded, true) . "\n"; +``` + +Example with EdDSA (libsodium and Ed25519 signature) +---------------------------- +```php +use Firebase\JWT\JWT; +use Firebase\JWT\Key; + +// Public and private keys are expected to be Base64 encoded. The last +// non-empty line is used so that keys can be generated with +// sodium_crypto_sign_keypair(). The secret keys generated by other tools may +// need to be adjusted to match the input expected by libsodium. + +$keyPair = sodium_crypto_sign_keypair(); + +$privateKey = base64_encode(sodium_crypto_sign_secretkey($keyPair)); + +$publicKey = base64_encode(sodium_crypto_sign_publickey($keyPair)); + +$payload = [ + 'iss' => 'example.org', + 'aud' => 'example.com', + 'iat' => 1356999524, + 'nbf' => 1357000000 +]; + +$jwt = JWT::encode($payload, $privateKey, 'EdDSA'); +echo "Encode:\n" . print_r($jwt, true) . "\n"; + +$decoded = JWT::decode($jwt, new Key($publicKey, 'EdDSA')); +echo "Decode:\n" . print_r((array) $decoded, true) . "\n"; +```` + +Example with multiple keys +-------------------------- +```php +use Firebase\JWT\JWT; +use Firebase\JWT\Key; + +// Example RSA keys from previous example +// $privateKey1 = '...'; +// $publicKey1 = '...'; + +// Example EdDSA keys from previous example +// $privateKey2 = '...'; +// $publicKey2 = '...'; + +$payload = [ + 'iss' => 'example.org', + 'aud' => 'example.com', + 'iat' => 1356999524, + 'nbf' => 1357000000 +]; + +$jwt1 = JWT::encode($payload, $privateKey1, 'RS256', 'kid1'); +$jwt2 = JWT::encode($payload, $privateKey2, 'EdDSA', 'kid2'); +echo "Encode 1:\n" . print_r($jwt1, true) . "\n"; +echo "Encode 2:\n" . print_r($jwt2, true) . "\n"; + +$keys = [ + 'kid1' => new Key($publicKey1, 'RS256'), + 'kid2' => new Key($publicKey2, 'EdDSA'), +]; + +$decoded1 = JWT::decode($jwt1, $keys); +$decoded2 = JWT::decode($jwt2, $keys); + +echo "Decode 1:\n" . print_r((array) $decoded1, true) . "\n"; +echo "Decode 2:\n" . print_r((array) $decoded2, true) . "\n"; +``` + +Using JWKs +---------- + +```php +use Firebase\JWT\JWK; +use Firebase\JWT\JWT; + +// Set of keys. The "keys" key is required. For example, the JSON response to +// this endpoint: https://www.gstatic.com/iap/verify/public_key-jwk +$jwks = ['keys' => []]; + +// JWK::parseKeySet($jwks) returns an associative array of **kid** to Firebase\JWT\Key +// objects. Pass this as the second parameter to JWT::decode. +JWT::decode($payload, JWK::parseKeySet($jwks)); +``` + +Using Cached Key Sets +--------------------- + +The `CachedKeySet` class can be used to fetch and cache JWKS (JSON Web Key Sets) from a public URI. +This has the following advantages: + +1. The results are cached for performance. +2. If an unrecognized key is requested, the cache is refreshed, to accomodate for key rotation. +3. If rate limiting is enabled, the JWKS URI will not make more than 10 requests a second. + +```php +use Firebase\JWT\CachedKeySet; +use Firebase\JWT\JWT; + +// The URI for the JWKS you wish to cache the results from +$jwksUri = 'https://www.gstatic.com/iap/verify/public_key-jwk'; + +// Create an HTTP client (can be any PSR-7 compatible HTTP client) +$httpClient = new GuzzleHttp\Client(); + +// Create an HTTP request factory (can be any PSR-17 compatible HTTP request factory) +$httpFactory = new GuzzleHttp\Psr\HttpFactory(); + +// Create a cache item pool (can be any PSR-6 compatible cache item pool) +$cacheItemPool = Phpfastcache\CacheManager::getInstance('files'); + +$keySet = new CachedKeySet( + $jwksUri, + $httpClient, + $httpFactory, + $cacheItemPool, + null, // $expiresAfter int seconds to set the JWKS to expire + true // $rateLimit true to enable rate limit of 10 RPS on lookup of invalid keys +); + +$jwt = 'eyJhbGci...'; // Some JWT signed by a key from the $jwkUri above +$decoded = JWT::decode($jwt, $keySet); +``` + +Miscellaneous +------------- + +#### Exception Handling + +When a call to `JWT::decode` is invalid, it will throw one of the following exceptions: + +```php +use Firebase\JWT\JWT; +use Firebase\JWT\SignatureInvalidException; +use Firebase\JWT\BeforeValidException; +use Firebase\JWT\ExpiredException; +use DomainException; +use InvalidArgumentException; +use UnexpectedValueException; + +try { + $decoded = JWT::decode($payload, $keys); +} catch (InvalidArgumentException $e) { + // provided key/key-array is empty or malformed. +} catch (DomainException $e) { + // provided algorithm is unsupported OR + // provided key is invalid OR + // unknown error thrown in openSSL or libsodium OR + // libsodium is required but not available. +} catch (SignatureInvalidException $e) { + // provided JWT signature verification failed. +} catch (BeforeValidException $e) { + // provided JWT is trying to be used before "nbf" claim OR + // provided JWT is trying to be used before "iat" claim. +} catch (ExpiredException $e) { + // provided JWT is trying to be used after "exp" claim. +} catch (UnexpectedValueException $e) { + // provided JWT is malformed OR + // provided JWT is missing an algorithm / using an unsupported algorithm OR + // provided JWT algorithm does not match provided key OR + // provided key ID in key/key-array is empty or invalid. +} +``` + +All exceptions in the `Firebase\JWT` namespace extend `UnexpectedValueException`, and can be simplified +like this: + +```php +use Firebase\JWT\JWT; +use UnexpectedValueException; +try { + $decoded = JWT::decode($payload, $keys); +} catch (LogicException $e) { + // errors having to do with environmental setup or malformed JWT Keys +} catch (UnexpectedValueException $e) { + // errors having to do with JWT signature and claims +} +``` + +#### Casting to array + +The return value of `JWT::decode` is the generic PHP object `stdClass`. If you'd like to handle with arrays +instead, you can do the following: + +```php +// return type is stdClass +$decoded = JWT::decode($payload, $keys); + +// cast to array +$decoded = json_decode(json_encode($decoded), true); +``` + +Tests +----- +Run the tests using phpunit: + +```bash +$ pear install PHPUnit +$ phpunit --configuration phpunit.xml.dist +PHPUnit 3.7.10 by Sebastian Bergmann. +..... +Time: 0 seconds, Memory: 2.50Mb +OK (5 tests, 5 assertions) +``` + +New Lines in private keys +----- + +If your private key contains `\n` characters, be sure to wrap it in double quotes `""` +and not single quotes `''` in order to properly interpret the escaped characters. + +License +------- +[3-Clause BSD](http://opensource.org/licenses/BSD-3-Clause). diff --git a/vendor/firebase/php-jwt/composer.json b/vendor/firebase/php-jwt/composer.json new file mode 100644 index 0000000..e23dfe3 --- /dev/null +++ b/vendor/firebase/php-jwt/composer.json @@ -0,0 +1,42 @@ +{ + "name": "firebase/php-jwt", + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": [ + "php", + "jwt" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "license": "BSD-3-Clause", + "require": { + "php": "^7.4||^8.0" + }, + "suggest": { + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present", + "ext-sodium": "Support EdDSA (Ed25519) signatures" + }, + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "require-dev": { + "guzzlehttp/guzzle": "^6.5||^7.4", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psr/cache": "^1.0||^2.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + } +} diff --git a/vendor/firebase/php-jwt/src/BeforeValidException.php b/vendor/firebase/php-jwt/src/BeforeValidException.php new file mode 100644 index 0000000..595164b --- /dev/null +++ b/vendor/firebase/php-jwt/src/BeforeValidException.php @@ -0,0 +1,18 @@ +payload = $payload; + } + + public function getPayload(): object + { + return $this->payload; + } +} diff --git a/vendor/firebase/php-jwt/src/CachedKeySet.php b/vendor/firebase/php-jwt/src/CachedKeySet.php new file mode 100644 index 0000000..ee529f9 --- /dev/null +++ b/vendor/firebase/php-jwt/src/CachedKeySet.php @@ -0,0 +1,268 @@ + + */ +class CachedKeySet implements ArrayAccess +{ + /** + * @var string + */ + private $jwksUri; + /** + * @var ClientInterface + */ + private $httpClient; + /** + * @var RequestFactoryInterface + */ + private $httpFactory; + /** + * @var CacheItemPoolInterface + */ + private $cache; + /** + * @var ?int + */ + private $expiresAfter; + /** + * @var ?CacheItemInterface + */ + private $cacheItem; + /** + * @var array> + */ + private $keySet; + /** + * @var string + */ + private $cacheKey; + /** + * @var string + */ + private $cacheKeyPrefix = 'jwks'; + /** + * @var int + */ + private $maxKeyLength = 64; + /** + * @var bool + */ + private $rateLimit; + /** + * @var string + */ + private $rateLimitCacheKey; + /** + * @var int + */ + private $maxCallsPerMinute = 10; + /** + * @var string|null + */ + private $defaultAlg; + + public function __construct( + string $jwksUri, + ClientInterface $httpClient, + RequestFactoryInterface $httpFactory, + CacheItemPoolInterface $cache, + int $expiresAfter = null, + bool $rateLimit = false, + string $defaultAlg = null + ) { + $this->jwksUri = $jwksUri; + $this->httpClient = $httpClient; + $this->httpFactory = $httpFactory; + $this->cache = $cache; + $this->expiresAfter = $expiresAfter; + $this->rateLimit = $rateLimit; + $this->defaultAlg = $defaultAlg; + $this->setCacheKeys(); + } + + /** + * @param string $keyId + * @return Key + */ + public function offsetGet($keyId): Key + { + if (!$this->keyIdExists($keyId)) { + throw new OutOfBoundsException('Key ID not found'); + } + return JWK::parseKey($this->keySet[$keyId], $this->defaultAlg); + } + + /** + * @param string $keyId + * @return bool + */ + public function offsetExists($keyId): bool + { + return $this->keyIdExists($keyId); + } + + /** + * @param string $offset + * @param Key $value + */ + public function offsetSet($offset, $value): void + { + throw new LogicException('Method not implemented'); + } + + /** + * @param string $offset + */ + public function offsetUnset($offset): void + { + throw new LogicException('Method not implemented'); + } + + /** + * @return array + */ + private function formatJwksForCache(string $jwks): array + { + $jwks = json_decode($jwks, true); + + if (!isset($jwks['keys'])) { + throw new UnexpectedValueException('"keys" member must exist in the JWK Set'); + } + + if (empty($jwks['keys'])) { + throw new InvalidArgumentException('JWK Set did not contain any keys'); + } + + $keys = []; + foreach ($jwks['keys'] as $k => $v) { + $kid = isset($v['kid']) ? $v['kid'] : $k; + $keys[(string) $kid] = $v; + } + + return $keys; + } + + private function keyIdExists(string $keyId): bool + { + if (null === $this->keySet) { + $item = $this->getCacheItem(); + // Try to load keys from cache + if ($item->isHit()) { + // item found! retrieve it + $this->keySet = $item->get(); + // If the cached item is a string, the JWKS response was cached (previous behavior). + // Parse this into expected format array instead. + if (\is_string($this->keySet)) { + $this->keySet = $this->formatJwksForCache($this->keySet); + } + } + } + + if (!isset($this->keySet[$keyId])) { + if ($this->rateLimitExceeded()) { + return false; + } + $request = $this->httpFactory->createRequest('GET', $this->jwksUri); + $jwksResponse = $this->httpClient->sendRequest($request); + if ($jwksResponse->getStatusCode() !== 200) { + throw new UnexpectedValueException( + sprintf('HTTP Error: %d %s for URI "%s"', + $jwksResponse->getStatusCode(), + $jwksResponse->getReasonPhrase(), + $this->jwksUri, + ), + $jwksResponse->getStatusCode() + ); + } + $this->keySet = $this->formatJwksForCache((string) $jwksResponse->getBody()); + + if (!isset($this->keySet[$keyId])) { + return false; + } + + $item = $this->getCacheItem(); + $item->set($this->keySet); + if ($this->expiresAfter) { + $item->expiresAfter($this->expiresAfter); + } + $this->cache->save($item); + } + + return true; + } + + private function rateLimitExceeded(): bool + { + if (!$this->rateLimit) { + return false; + } + + $cacheItem = $this->cache->getItem($this->rateLimitCacheKey); + if (!$cacheItem->isHit()) { + $cacheItem->expiresAfter(1); // # of calls are cached each minute + } + + $callsPerMinute = (int) $cacheItem->get(); + if (++$callsPerMinute > $this->maxCallsPerMinute) { + return true; + } + $cacheItem->set($callsPerMinute); + $this->cache->save($cacheItem); + return false; + } + + private function getCacheItem(): CacheItemInterface + { + if (\is_null($this->cacheItem)) { + $this->cacheItem = $this->cache->getItem($this->cacheKey); + } + + return $this->cacheItem; + } + + private function setCacheKeys(): void + { + if (empty($this->jwksUri)) { + throw new RuntimeException('JWKS URI is empty'); + } + + // ensure we do not have illegal characters + $key = preg_replace('|[^a-zA-Z0-9_\.!]|', '', $this->jwksUri); + + // add prefix + $key = $this->cacheKeyPrefix . $key; + + // Hash keys if they exceed $maxKeyLength of 64 + if (\strlen($key) > $this->maxKeyLength) { + $key = substr(hash('sha256', $key), 0, $this->maxKeyLength); + } + + $this->cacheKey = $key; + + if ($this->rateLimit) { + // add prefix + $rateLimitKey = $this->cacheKeyPrefix . 'ratelimit' . $key; + + // Hash keys if they exceed $maxKeyLength of 64 + if (\strlen($rateLimitKey) > $this->maxKeyLength) { + $rateLimitKey = substr(hash('sha256', $rateLimitKey), 0, $this->maxKeyLength); + } + + $this->rateLimitCacheKey = $rateLimitKey; + } + } +} diff --git a/vendor/firebase/php-jwt/src/ExpiredException.php b/vendor/firebase/php-jwt/src/ExpiredException.php new file mode 100644 index 0000000..12fef09 --- /dev/null +++ b/vendor/firebase/php-jwt/src/ExpiredException.php @@ -0,0 +1,18 @@ +payload = $payload; + } + + public function getPayload(): object + { + return $this->payload; + } +} diff --git a/vendor/firebase/php-jwt/src/JWK.php b/vendor/firebase/php-jwt/src/JWK.php new file mode 100644 index 0000000..63fb248 --- /dev/null +++ b/vendor/firebase/php-jwt/src/JWK.php @@ -0,0 +1,349 @@ + + * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD + * @link https://github.com/firebase/php-jwt + */ +class JWK +{ + private const OID = '1.2.840.10045.2.1'; + private const ASN1_OBJECT_IDENTIFIER = 0x06; + private const ASN1_SEQUENCE = 0x10; // also defined in JWT + private const ASN1_BIT_STRING = 0x03; + private const EC_CURVES = [ + 'P-256' => '1.2.840.10045.3.1.7', // Len: 64 + 'secp256k1' => '1.3.132.0.10', // Len: 64 + 'P-384' => '1.3.132.0.34', // Len: 96 + // 'P-521' => '1.3.132.0.35', // Len: 132 (not supported) + ]; + + // For keys with "kty" equal to "OKP" (Octet Key Pair), the "crv" parameter must contain the key subtype. + // This library supports the following subtypes: + private const OKP_SUBTYPES = [ + 'Ed25519' => true, // RFC 8037 + ]; + + /** + * Parse a set of JWK keys + * + * @param array $jwks The JSON Web Key Set as an associative array + * @param string $defaultAlg The algorithm for the Key object if "alg" is not set in the + * JSON Web Key Set + * + * @return array An associative array of key IDs (kid) to Key objects + * + * @throws InvalidArgumentException Provided JWK Set is empty + * @throws UnexpectedValueException Provided JWK Set was invalid + * @throws DomainException OpenSSL failure + * + * @uses parseKey + */ + public static function parseKeySet(array $jwks, string $defaultAlg = null): array + { + $keys = []; + + if (!isset($jwks['keys'])) { + throw new UnexpectedValueException('"keys" member must exist in the JWK Set'); + } + + if (empty($jwks['keys'])) { + throw new InvalidArgumentException('JWK Set did not contain any keys'); + } + + foreach ($jwks['keys'] as $k => $v) { + $kid = isset($v['kid']) ? $v['kid'] : $k; + if ($key = self::parseKey($v, $defaultAlg)) { + $keys[(string) $kid] = $key; + } + } + + if (0 === \count($keys)) { + throw new UnexpectedValueException('No supported algorithms found in JWK Set'); + } + + return $keys; + } + + /** + * Parse a JWK key + * + * @param array $jwk An individual JWK + * @param string $defaultAlg The algorithm for the Key object if "alg" is not set in the + * JSON Web Key Set + * + * @return Key The key object for the JWK + * + * @throws InvalidArgumentException Provided JWK is empty + * @throws UnexpectedValueException Provided JWK was invalid + * @throws DomainException OpenSSL failure + * + * @uses createPemFromModulusAndExponent + */ + public static function parseKey(array $jwk, string $defaultAlg = null): ?Key + { + if (empty($jwk)) { + throw new InvalidArgumentException('JWK must not be empty'); + } + + if (!isset($jwk['kty'])) { + throw new UnexpectedValueException('JWK must contain a "kty" parameter'); + } + + if (!isset($jwk['alg'])) { + if (\is_null($defaultAlg)) { + // The "alg" parameter is optional in a KTY, but an algorithm is required + // for parsing in this library. Use the $defaultAlg parameter when parsing the + // key set in order to prevent this error. + // @see https://datatracker.ietf.org/doc/html/rfc7517#section-4.4 + throw new UnexpectedValueException('JWK must contain an "alg" parameter'); + } + $jwk['alg'] = $defaultAlg; + } + + switch ($jwk['kty']) { + case 'RSA': + if (!empty($jwk['d'])) { + throw new UnexpectedValueException('RSA private keys are not supported'); + } + if (!isset($jwk['n']) || !isset($jwk['e'])) { + throw new UnexpectedValueException('RSA keys must contain values for both "n" and "e"'); + } + + $pem = self::createPemFromModulusAndExponent($jwk['n'], $jwk['e']); + $publicKey = \openssl_pkey_get_public($pem); + if (false === $publicKey) { + throw new DomainException( + 'OpenSSL error: ' . \openssl_error_string() + ); + } + return new Key($publicKey, $jwk['alg']); + case 'EC': + if (isset($jwk['d'])) { + // The key is actually a private key + throw new UnexpectedValueException('Key data must be for a public key'); + } + + if (empty($jwk['crv'])) { + throw new UnexpectedValueException('crv not set'); + } + + if (!isset(self::EC_CURVES[$jwk['crv']])) { + throw new DomainException('Unrecognised or unsupported EC curve'); + } + + if (empty($jwk['x']) || empty($jwk['y'])) { + throw new UnexpectedValueException('x and y not set'); + } + + $publicKey = self::createPemFromCrvAndXYCoordinates($jwk['crv'], $jwk['x'], $jwk['y']); + return new Key($publicKey, $jwk['alg']); + case 'OKP': + if (isset($jwk['d'])) { + // The key is actually a private key + throw new UnexpectedValueException('Key data must be for a public key'); + } + + if (!isset($jwk['crv'])) { + throw new UnexpectedValueException('crv not set'); + } + + if (empty(self::OKP_SUBTYPES[$jwk['crv']])) { + throw new DomainException('Unrecognised or unsupported OKP key subtype'); + } + + if (empty($jwk['x'])) { + throw new UnexpectedValueException('x not set'); + } + + // This library works internally with EdDSA keys (Ed25519) encoded in standard base64. + $publicKey = JWT::convertBase64urlToBase64($jwk['x']); + return new Key($publicKey, $jwk['alg']); + default: + break; + } + + return null; + } + + /** + * Converts the EC JWK values to pem format. + * + * @param string $crv The EC curve (only P-256 & P-384 is supported) + * @param string $x The EC x-coordinate + * @param string $y The EC y-coordinate + * + * @return string + */ + private static function createPemFromCrvAndXYCoordinates(string $crv, string $x, string $y): string + { + $pem = + self::encodeDER( + self::ASN1_SEQUENCE, + self::encodeDER( + self::ASN1_SEQUENCE, + self::encodeDER( + self::ASN1_OBJECT_IDENTIFIER, + self::encodeOID(self::OID) + ) + . self::encodeDER( + self::ASN1_OBJECT_IDENTIFIER, + self::encodeOID(self::EC_CURVES[$crv]) + ) + ) . + self::encodeDER( + self::ASN1_BIT_STRING, + \chr(0x00) . \chr(0x04) + . JWT::urlsafeB64Decode($x) + . JWT::urlsafeB64Decode($y) + ) + ); + + return sprintf( + "-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----\n", + wordwrap(base64_encode($pem), 64, "\n", true) + ); + } + + /** + * Create a public key represented in PEM format from RSA modulus and exponent information + * + * @param string $n The RSA modulus encoded in Base64 + * @param string $e The RSA exponent encoded in Base64 + * + * @return string The RSA public key represented in PEM format + * + * @uses encodeLength + */ + private static function createPemFromModulusAndExponent( + string $n, + string $e + ): string { + $mod = JWT::urlsafeB64Decode($n); + $exp = JWT::urlsafeB64Decode($e); + + $modulus = \pack('Ca*a*', 2, self::encodeLength(\strlen($mod)), $mod); + $publicExponent = \pack('Ca*a*', 2, self::encodeLength(\strlen($exp)), $exp); + + $rsaPublicKey = \pack( + 'Ca*a*a*', + 48, + self::encodeLength(\strlen($modulus) + \strlen($publicExponent)), + $modulus, + $publicExponent + ); + + // sequence(oid(1.2.840.113549.1.1.1), null)) = rsaEncryption. + $rsaOID = \pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA + $rsaPublicKey = \chr(0) . $rsaPublicKey; + $rsaPublicKey = \chr(3) . self::encodeLength(\strlen($rsaPublicKey)) . $rsaPublicKey; + + $rsaPublicKey = \pack( + 'Ca*a*', + 48, + self::encodeLength(\strlen($rsaOID . $rsaPublicKey)), + $rsaOID . $rsaPublicKey + ); + + return "-----BEGIN PUBLIC KEY-----\r\n" . + \chunk_split(\base64_encode($rsaPublicKey), 64) . + '-----END PUBLIC KEY-----'; + } + + /** + * DER-encode the length + * + * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See + * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information. + * + * @param int $length + * @return string + */ + private static function encodeLength(int $length): string + { + if ($length <= 0x7F) { + return \chr($length); + } + + $temp = \ltrim(\pack('N', $length), \chr(0)); + + return \pack('Ca*', 0x80 | \strlen($temp), $temp); + } + + /** + * Encodes a value into a DER object. + * Also defined in Firebase\JWT\JWT + * + * @param int $type DER tag + * @param string $value the value to encode + * @return string the encoded object + */ + private static function encodeDER(int $type, string $value): string + { + $tag_header = 0; + if ($type === self::ASN1_SEQUENCE) { + $tag_header |= 0x20; + } + + // Type + $der = \chr($tag_header | $type); + + // Length + $der .= \chr(\strlen($value)); + + return $der . $value; + } + + /** + * Encodes a string into a DER-encoded OID. + * + * @param string $oid the OID string + * @return string the binary DER-encoded OID + */ + private static function encodeOID(string $oid): string + { + $octets = explode('.', $oid); + + // Get the first octet + $first = (int) array_shift($octets); + $second = (int) array_shift($octets); + $oid = \chr($first * 40 + $second); + + // Iterate over subsequent octets + foreach ($octets as $octet) { + if ($octet == 0) { + $oid .= \chr(0x00); + continue; + } + $bin = ''; + + while ($octet) { + $bin .= \chr(0x80 | ($octet & 0x7f)); + $octet >>= 7; + } + $bin[0] = $bin[0] & \chr(0x7f); + + // Convert to big endian if necessary + if (pack('V', 65534) == pack('L', 65534)) { + $oid .= strrev($bin); + } else { + $oid .= $bin; + } + } + + return $oid; + } +} diff --git a/vendor/firebase/php-jwt/src/JWT.php b/vendor/firebase/php-jwt/src/JWT.php new file mode 100644 index 0000000..2634920 --- /dev/null +++ b/vendor/firebase/php-jwt/src/JWT.php @@ -0,0 +1,669 @@ + + * @author Anant Narayanan + * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD + * @link https://github.com/firebase/php-jwt + */ +class JWT +{ + private const ASN1_INTEGER = 0x02; + private const ASN1_SEQUENCE = 0x10; + private const ASN1_BIT_STRING = 0x03; + + /** + * When checking nbf, iat or expiration times, + * we want to provide some extra leeway time to + * account for clock skew. + * + * @var int + */ + public static $leeway = 0; + + /** + * Allow the current timestamp to be specified. + * Useful for fixing a value within unit testing. + * Will default to PHP time() value if null. + * + * @var ?int + */ + public static $timestamp = null; + + /** + * @var array + */ + public static $supported_algs = [ + 'ES384' => ['openssl', 'SHA384'], + 'ES256' => ['openssl', 'SHA256'], + 'ES256K' => ['openssl', 'SHA256'], + 'HS256' => ['hash_hmac', 'SHA256'], + 'HS384' => ['hash_hmac', 'SHA384'], + 'HS512' => ['hash_hmac', 'SHA512'], + 'RS256' => ['openssl', 'SHA256'], + 'RS384' => ['openssl', 'SHA384'], + 'RS512' => ['openssl', 'SHA512'], + 'EdDSA' => ['sodium_crypto', 'EdDSA'], + ]; + + /** + * Decodes a JWT string into a PHP object. + * + * @param string $jwt The JWT + * @param Key|ArrayAccess|array $keyOrKeyArray The Key or associative array of key IDs + * (kid) to Key objects. + * If the algorithm used is asymmetric, this is + * the public key. + * Each Key object contains an algorithm and + * matching key. + * Supported algorithms are 'ES384','ES256', + * 'HS256', 'HS384', 'HS512', 'RS256', 'RS384' + * and 'RS512'. + * @param stdClass $headers Optional. Populates stdClass with headers. + * + * @return stdClass The JWT's payload as a PHP object + * + * @throws InvalidArgumentException Provided key/key-array was empty or malformed + * @throws DomainException Provided JWT is malformed + * @throws UnexpectedValueException Provided JWT was invalid + * @throws SignatureInvalidException Provided JWT was invalid because the signature verification failed + * @throws BeforeValidException Provided JWT is trying to be used before it's eligible as defined by 'nbf' + * @throws BeforeValidException Provided JWT is trying to be used before it's been created as defined by 'iat' + * @throws ExpiredException Provided JWT has since expired, as defined by the 'exp' claim + * + * @uses jsonDecode + * @uses urlsafeB64Decode + */ + public static function decode( + string $jwt, + $keyOrKeyArray, + stdClass &$headers = null + ): stdClass { + // Validate JWT + $timestamp = \is_null(static::$timestamp) ? \time() : static::$timestamp; + + if (empty($keyOrKeyArray)) { + throw new InvalidArgumentException('Key may not be empty'); + } + $tks = \explode('.', $jwt); + if (\count($tks) !== 3) { + throw new UnexpectedValueException('Wrong number of segments'); + } + list($headb64, $bodyb64, $cryptob64) = $tks; + $headerRaw = static::urlsafeB64Decode($headb64); + if (null === ($header = static::jsonDecode($headerRaw))) { + throw new UnexpectedValueException('Invalid header encoding'); + } + if ($headers !== null) { + $headers = $header; + } + $payloadRaw = static::urlsafeB64Decode($bodyb64); + if (null === ($payload = static::jsonDecode($payloadRaw))) { + throw new UnexpectedValueException('Invalid claims encoding'); + } + if (\is_array($payload)) { + // prevent PHP Fatal Error in edge-cases when payload is empty array + $payload = (object) $payload; + } + if (!$payload instanceof stdClass) { + throw new UnexpectedValueException('Payload must be a JSON object'); + } + $sig = static::urlsafeB64Decode($cryptob64); + if (empty($header->alg)) { + throw new UnexpectedValueException('Empty algorithm'); + } + if (empty(static::$supported_algs[$header->alg])) { + throw new UnexpectedValueException('Algorithm not supported'); + } + + $key = self::getKey($keyOrKeyArray, property_exists($header, 'kid') ? $header->kid : null); + + // Check the algorithm + if (!self::constantTimeEquals($key->getAlgorithm(), $header->alg)) { + // See issue #351 + throw new UnexpectedValueException('Incorrect key for this algorithm'); + } + if (\in_array($header->alg, ['ES256', 'ES256K', 'ES384'], true)) { + // OpenSSL expects an ASN.1 DER sequence for ES256/ES256K/ES384 signatures + $sig = self::signatureToDER($sig); + } + if (!self::verify("{$headb64}.{$bodyb64}", $sig, $key->getKeyMaterial(), $header->alg)) { + throw new SignatureInvalidException('Signature verification failed'); + } + + // Check the nbf if it is defined. This is the time that the + // token can actually be used. If it's not yet that time, abort. + if (isset($payload->nbf) && floor($payload->nbf) > ($timestamp + static::$leeway)) { + $ex = new BeforeValidException( + 'Cannot handle token with nbf prior to ' . \date(DateTime::ISO8601, (int) $payload->nbf) + ); + $ex->setPayload($payload); + throw $ex; + } + + // Check that this token has been created before 'now'. This prevents + // using tokens that have been created for later use (and haven't + // correctly used the nbf claim). + if (!isset($payload->nbf) && isset($payload->iat) && floor($payload->iat) > ($timestamp + static::$leeway)) { + $ex = new BeforeValidException( + 'Cannot handle token with iat prior to ' . \date(DateTime::ISO8601, (int) $payload->iat) + ); + $ex->setPayload($payload); + throw $ex; + } + + // Check if this token has expired. + if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) { + $ex = new ExpiredException('Expired token'); + $ex->setPayload($payload); + throw $ex; + } + + return $payload; + } + + /** + * Converts and signs a PHP array into a JWT string. + * + * @param array $payload PHP array + * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key. + * @param string $alg Supported algorithms are 'ES384','ES256', 'ES256K', 'HS256', + * 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512' + * @param string $keyId + * @param array $head An array with header elements to attach + * + * @return string A signed JWT + * + * @uses jsonEncode + * @uses urlsafeB64Encode + */ + public static function encode( + array $payload, + $key, + string $alg, + string $keyId = null, + array $head = null + ): string { + $header = ['typ' => 'JWT']; + if (isset($head) && \is_array($head)) { + $header = \array_merge($header, $head); + } + $header['alg'] = $alg; + if ($keyId !== null) { + $header['kid'] = $keyId; + } + $segments = []; + $segments[] = static::urlsafeB64Encode((string) static::jsonEncode($header)); + $segments[] = static::urlsafeB64Encode((string) static::jsonEncode($payload)); + $signing_input = \implode('.', $segments); + + $signature = static::sign($signing_input, $key, $alg); + $segments[] = static::urlsafeB64Encode($signature); + + return \implode('.', $segments); + } + + /** + * Sign a string with a given key and algorithm. + * + * @param string $msg The message to sign + * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key. + * @param string $alg Supported algorithms are 'EdDSA', 'ES384', 'ES256', 'ES256K', 'HS256', + * 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512' + * + * @return string An encrypted message + * + * @throws DomainException Unsupported algorithm or bad key was specified + */ + public static function sign( + string $msg, + $key, + string $alg + ): string { + if (empty(static::$supported_algs[$alg])) { + throw new DomainException('Algorithm not supported'); + } + list($function, $algorithm) = static::$supported_algs[$alg]; + switch ($function) { + case 'hash_hmac': + if (!\is_string($key)) { + throw new InvalidArgumentException('key must be a string when using hmac'); + } + return \hash_hmac($algorithm, $msg, $key, true); + case 'openssl': + $signature = ''; + $success = \openssl_sign($msg, $signature, $key, $algorithm); // @phpstan-ignore-line + if (!$success) { + throw new DomainException('OpenSSL unable to sign data'); + } + if ($alg === 'ES256' || $alg === 'ES256K') { + $signature = self::signatureFromDER($signature, 256); + } elseif ($alg === 'ES384') { + $signature = self::signatureFromDER($signature, 384); + } + return $signature; + case 'sodium_crypto': + if (!\function_exists('sodium_crypto_sign_detached')) { + throw new DomainException('libsodium is not available'); + } + if (!\is_string($key)) { + throw new InvalidArgumentException('key must be a string when using EdDSA'); + } + try { + // The last non-empty line is used as the key. + $lines = array_filter(explode("\n", $key)); + $key = base64_decode((string) end($lines)); + if (\strlen($key) === 0) { + throw new DomainException('Key cannot be empty string'); + } + return sodium_crypto_sign_detached($msg, $key); + } catch (Exception $e) { + throw new DomainException($e->getMessage(), 0, $e); + } + } + + throw new DomainException('Algorithm not supported'); + } + + /** + * Verify a signature with the message, key and method. Not all methods + * are symmetric, so we must have a separate verify and sign method. + * + * @param string $msg The original message (header and body) + * @param string $signature The original signature + * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial For Ed*, ES*, HS*, a string key works. for RS*, must be an instance of OpenSSLAsymmetricKey + * @param string $alg The algorithm + * + * @return bool + * + * @throws DomainException Invalid Algorithm, bad key, or OpenSSL failure + */ + private static function verify( + string $msg, + string $signature, + $keyMaterial, + string $alg + ): bool { + if (empty(static::$supported_algs[$alg])) { + throw new DomainException('Algorithm not supported'); + } + + list($function, $algorithm) = static::$supported_algs[$alg]; + switch ($function) { + case 'openssl': + $success = \openssl_verify($msg, $signature, $keyMaterial, $algorithm); // @phpstan-ignore-line + if ($success === 1) { + return true; + } + if ($success === 0) { + return false; + } + // returns 1 on success, 0 on failure, -1 on error. + throw new DomainException( + 'OpenSSL error: ' . \openssl_error_string() + ); + case 'sodium_crypto': + if (!\function_exists('sodium_crypto_sign_verify_detached')) { + throw new DomainException('libsodium is not available'); + } + if (!\is_string($keyMaterial)) { + throw new InvalidArgumentException('key must be a string when using EdDSA'); + } + try { + // The last non-empty line is used as the key. + $lines = array_filter(explode("\n", $keyMaterial)); + $key = base64_decode((string) end($lines)); + if (\strlen($key) === 0) { + throw new DomainException('Key cannot be empty string'); + } + if (\strlen($signature) === 0) { + throw new DomainException('Signature cannot be empty string'); + } + return sodium_crypto_sign_verify_detached($signature, $msg, $key); + } catch (Exception $e) { + throw new DomainException($e->getMessage(), 0, $e); + } + case 'hash_hmac': + default: + if (!\is_string($keyMaterial)) { + throw new InvalidArgumentException('key must be a string when using hmac'); + } + $hash = \hash_hmac($algorithm, $msg, $keyMaterial, true); + return self::constantTimeEquals($hash, $signature); + } + } + + /** + * Decode a JSON string into a PHP object. + * + * @param string $input JSON string + * + * @return mixed The decoded JSON string + * + * @throws DomainException Provided string was invalid JSON + */ + public static function jsonDecode(string $input) + { + $obj = \json_decode($input, false, 512, JSON_BIGINT_AS_STRING); + + if ($errno = \json_last_error()) { + self::handleJsonError($errno); + } elseif ($obj === null && $input !== 'null') { + throw new DomainException('Null result with non-null input'); + } + return $obj; + } + + /** + * Encode a PHP array into a JSON string. + * + * @param array $input A PHP array + * + * @return string JSON representation of the PHP array + * + * @throws DomainException Provided object could not be encoded to valid JSON + */ + public static function jsonEncode(array $input): string + { + if (PHP_VERSION_ID >= 50400) { + $json = \json_encode($input, \JSON_UNESCAPED_SLASHES); + } else { + // PHP 5.3 only + $json = \json_encode($input); + } + if ($errno = \json_last_error()) { + self::handleJsonError($errno); + } elseif ($json === 'null') { + throw new DomainException('Null result with non-null input'); + } + if ($json === false) { + throw new DomainException('Provided object could not be encoded to valid JSON'); + } + return $json; + } + + /** + * Decode a string with URL-safe Base64. + * + * @param string $input A Base64 encoded string + * + * @return string A decoded string + * + * @throws InvalidArgumentException invalid base64 characters + */ + public static function urlsafeB64Decode(string $input): string + { + return \base64_decode(self::convertBase64UrlToBase64($input)); + } + + /** + * Convert a string in the base64url (URL-safe Base64) encoding to standard base64. + * + * @param string $input A Base64 encoded string with URL-safe characters (-_ and no padding) + * + * @return string A Base64 encoded string with standard characters (+/) and padding (=), when + * needed. + * + * @see https://www.rfc-editor.org/rfc/rfc4648 + */ + public static function convertBase64UrlToBase64(string $input): string + { + $remainder = \strlen($input) % 4; + if ($remainder) { + $padlen = 4 - $remainder; + $input .= \str_repeat('=', $padlen); + } + return \strtr($input, '-_', '+/'); + } + + /** + * Encode a string with URL-safe Base64. + * + * @param string $input The string you want encoded + * + * @return string The base64 encode of what you passed in + */ + public static function urlsafeB64Encode(string $input): string + { + return \str_replace('=', '', \strtr(\base64_encode($input), '+/', '-_')); + } + + + /** + * Determine if an algorithm has been provided for each Key + * + * @param Key|ArrayAccess|array $keyOrKeyArray + * @param string|null $kid + * + * @throws UnexpectedValueException + * + * @return Key + */ + private static function getKey( + $keyOrKeyArray, + ?string $kid + ): Key { + if ($keyOrKeyArray instanceof Key) { + return $keyOrKeyArray; + } + + if (empty($kid) && $kid !== '0') { + throw new UnexpectedValueException('"kid" empty, unable to lookup correct key'); + } + + if ($keyOrKeyArray instanceof CachedKeySet) { + // Skip "isset" check, as this will automatically refresh if not set + return $keyOrKeyArray[$kid]; + } + + if (!isset($keyOrKeyArray[$kid])) { + throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key'); + } + + return $keyOrKeyArray[$kid]; + } + + /** + * @param string $left The string of known length to compare against + * @param string $right The user-supplied string + * @return bool + */ + public static function constantTimeEquals(string $left, string $right): bool + { + if (\function_exists('hash_equals')) { + return \hash_equals($left, $right); + } + $len = \min(self::safeStrlen($left), self::safeStrlen($right)); + + $status = 0; + for ($i = 0; $i < $len; $i++) { + $status |= (\ord($left[$i]) ^ \ord($right[$i])); + } + $status |= (self::safeStrlen($left) ^ self::safeStrlen($right)); + + return ($status === 0); + } + + /** + * Helper method to create a JSON error. + * + * @param int $errno An error number from json_last_error() + * + * @throws DomainException + * + * @return void + */ + private static function handleJsonError(int $errno): void + { + $messages = [ + JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', + JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON', + JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', + JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON', + JSON_ERROR_UTF8 => 'Malformed UTF-8 characters' //PHP >= 5.3.3 + ]; + throw new DomainException( + isset($messages[$errno]) + ? $messages[$errno] + : 'Unknown JSON error: ' . $errno + ); + } + + /** + * Get the number of bytes in cryptographic strings. + * + * @param string $str + * + * @return int + */ + private static function safeStrlen(string $str): int + { + if (\function_exists('mb_strlen')) { + return \mb_strlen($str, '8bit'); + } + return \strlen($str); + } + + /** + * Convert an ECDSA signature to an ASN.1 DER sequence + * + * @param string $sig The ECDSA signature to convert + * @return string The encoded DER object + */ + private static function signatureToDER(string $sig): string + { + // Separate the signature into r-value and s-value + $length = max(1, (int) (\strlen($sig) / 2)); + list($r, $s) = \str_split($sig, $length); + + // Trim leading zeros + $r = \ltrim($r, "\x00"); + $s = \ltrim($s, "\x00"); + + // Convert r-value and s-value from unsigned big-endian integers to + // signed two's complement + if (\ord($r[0]) > 0x7f) { + $r = "\x00" . $r; + } + if (\ord($s[0]) > 0x7f) { + $s = "\x00" . $s; + } + + return self::encodeDER( + self::ASN1_SEQUENCE, + self::encodeDER(self::ASN1_INTEGER, $r) . + self::encodeDER(self::ASN1_INTEGER, $s) + ); + } + + /** + * Encodes a value into a DER object. + * + * @param int $type DER tag + * @param string $value the value to encode + * + * @return string the encoded object + */ + private static function encodeDER(int $type, string $value): string + { + $tag_header = 0; + if ($type === self::ASN1_SEQUENCE) { + $tag_header |= 0x20; + } + + // Type + $der = \chr($tag_header | $type); + + // Length + $der .= \chr(\strlen($value)); + + return $der . $value; + } + + /** + * Encodes signature from a DER object. + * + * @param string $der binary signature in DER format + * @param int $keySize the number of bits in the key + * + * @return string the signature + */ + private static function signatureFromDER(string $der, int $keySize): string + { + // OpenSSL returns the ECDSA signatures as a binary ASN.1 DER SEQUENCE + list($offset, $_) = self::readDER($der); + list($offset, $r) = self::readDER($der, $offset); + list($offset, $s) = self::readDER($der, $offset); + + // Convert r-value and s-value from signed two's compliment to unsigned + // big-endian integers + $r = \ltrim($r, "\x00"); + $s = \ltrim($s, "\x00"); + + // Pad out r and s so that they are $keySize bits long + $r = \str_pad($r, $keySize / 8, "\x00", STR_PAD_LEFT); + $s = \str_pad($s, $keySize / 8, "\x00", STR_PAD_LEFT); + + return $r . $s; + } + + /** + * Reads binary DER-encoded data and decodes into a single object + * + * @param string $der the binary data in DER format + * @param int $offset the offset of the data stream containing the object + * to decode + * + * @return array{int, string|null} the new offset and the decoded object + */ + private static function readDER(string $der, int $offset = 0): array + { + $pos = $offset; + $size = \strlen($der); + $constructed = (\ord($der[$pos]) >> 5) & 0x01; + $type = \ord($der[$pos++]) & 0x1f; + + // Length + $len = \ord($der[$pos++]); + if ($len & 0x80) { + $n = $len & 0x1f; + $len = 0; + while ($n-- && $pos < $size) { + $len = ($len << 8) | \ord($der[$pos++]); + } + } + + // Value + if ($type === self::ASN1_BIT_STRING) { + $pos++; // Skip the first contents octet (padding indicator) + $data = \substr($der, $pos, $len - 1); + $pos += $len - 1; + } elseif (!$constructed) { + $data = \substr($der, $pos, $len); + $pos += $len; + } else { + $data = null; + } + + return [$pos, $data]; + } +} diff --git a/vendor/firebase/php-jwt/src/JWTExceptionWithPayloadInterface.php b/vendor/firebase/php-jwt/src/JWTExceptionWithPayloadInterface.php new file mode 100644 index 0000000..7933ed6 --- /dev/null +++ b/vendor/firebase/php-jwt/src/JWTExceptionWithPayloadInterface.php @@ -0,0 +1,20 @@ +keyMaterial = $keyMaterial; + $this->algorithm = $algorithm; + } + + /** + * Return the algorithm valid for this key + * + * @return string + */ + public function getAlgorithm(): string + { + return $this->algorithm; + } + + /** + * @return string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate + */ + public function getKeyMaterial() + { + return $this->keyMaterial; + } +} diff --git a/vendor/firebase/php-jwt/src/SignatureInvalidException.php b/vendor/firebase/php-jwt/src/SignatureInvalidException.php new file mode 100644 index 0000000..d35dee9 --- /dev/null +++ b/vendor/firebase/php-jwt/src/SignatureInvalidException.php @@ -0,0 +1,7 @@ + Date: Thu, 21 Mar 2024 18:21:46 -0500 Subject: [PATCH 2/7] =?UTF-8?q?Encapsulaci=C3=B3n=20variables=20de=20entor?= =?UTF-8?q?no?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/login.php | 158 ++++++++++++++++--------------- config/database.php | 46 ++++----- config/environment_variables.php | 35 +++++++ 3 files changed, 138 insertions(+), 101 deletions(-) create mode 100644 config/environment_variables.php diff --git a/api/login.php b/api/login.php index 8ffb6e7..ad3ac53 100644 --- a/api/login.php +++ b/api/login.php @@ -1,79 +1,81 @@ -getConnection(); - -$data = json_decode(file_get_contents("php://input")); - -$email_user = $data->email_user; -$passw_user = $data->passw_user; - -$table_name = 'users'; - -$query = "SELECT id_user, nomb_user, apell_user, passw_user FROM " . $table_name . " WHERE email_user = ? LIMIT 0,1"; - -$stmt = $conn->prepare( $query ); -$stmt->bindParam(1, $email_user); -$stmt->execute(); -$num = $stmt->rowCount(); - -if($num > 0){ - $row = $stmt->fetch(PDO::FETCH_ASSOC); - $id_user = $row['id_user']; - $nomb_user = $row['nomb_user']; - $apell_user = $row['apell_user']; - $passw_db = $row['passw_user']; - - if(password_verify($passw_user, $passw_db)) - { - $secret_key = "123api"; - $issuer_claim = "localhost"; // this can be the servername - $audience_claim = "THE_AUDIENCE"; - $issuedat_claim = time(); // issued at - $notbefore_claim = $issuedat_claim + 1; //not before in seconds - $expire_claim = $issuedat_claim + 90000; // expire time in seconds - // $now = strtotime("now"); - $token = array( - "iss" => $issuer_claim, - "aud" => $audience_claim, - "iat" => $issuedat_claim, - "nbf" => $notbefore_claim, - "exp" => $expire_claim, - "data" => array( - "id_user" => $id_user, - "nomb_user" => $nomb_user, - "apell_user" => $apell_user, - "email_user" => $email_user - )); - - http_response_code(200); - - $jwt = JWT::encode($token, $secret_key, 'HS256'); - echo json_encode( - array( - "message" => "Inicio de sesión satisfactorio.", - "jwt" => $jwt, - "email" => $email_user, - "expireAt" => $expire_claim - )); - } - else{ - - http_response_code(401); - echo json_encode(array("message" => "Inicio de sesión fallido.")); - } -} +getConnection(); + +$data = json_decode(file_get_contents("php://input")); + +$email_user = $data->email_user; +$passw_user = $data->passw_user; + +$table_name = 'users'; + +$query = "SELECT id_user, nomb_user, apell_user, passw_user FROM " . $table_name . " WHERE email_user = ? LIMIT 0,1"; + +$stmt = $conn->prepare( $query ); +$stmt->bindParam(1, $email_user); +$stmt->execute(); +$num = $stmt->rowCount(); + +if($num > 0){ + $row = $stmt->fetch(PDO::FETCH_ASSOC); + $id_user = $row['id_user']; + $nomb_user = $row['nomb_user']; + $apell_user = $row['apell_user']; + $passw_db = $row['passw_user']; + + if(password_verify($passw_user, $passw_db)) + { + $secret_key = $env_variables->getKeyJwt(); + $issuer_claim = "localhost"; // this can be the servername + $audience_claim = "THE_AUDIENCE"; + $issuedat_claim = time(); // issued at + $notbefore_claim = $issuedat_claim + 1; //not before in seconds + $expire_claim = $issuedat_claim + 90000; // expire time in seconds + // $now = strtotime("now"); + $token = array( + "iss" => $issuer_claim, + "aud" => $audience_claim, + "iat" => $issuedat_claim, + "nbf" => $notbefore_claim, + "exp" => $expire_claim, + "data" => array( + "id_user" => $id_user, + "nomb_user" => $nomb_user, + "apell_user" => $apell_user, + "email_user" => $email_user + )); + + http_response_code(200); + + $jwt = JWT::encode($token, $secret_key, 'HS256'); + echo json_encode( + array( + "message" => "Inicio de sesión satisfactorio.", + "jwt" => $jwt, + "email" => $email_user, + "expireAt" => $expire_claim + )); + } + else{ + + http_response_code(401); + echo json_encode(array("message" => "Inicio de sesión fallido.")); + } +} ?> \ No newline at end of file diff --git a/config/database.php b/config/database.php index e5954ab..d544df6 100644 --- a/config/database.php +++ b/config/database.php @@ -1,24 +1,24 @@ -connection = null; - - try{ - $this->connection = new PDO("mysql:host=" . $this->db_host . ";dbname=" . $this->db_name, $this->db_user, $this->db_password); - }catch(PDOException $exception){ - echo "Connection failed: " . $exception->getMessage(); - } - - return $this->connection; - } -} +connection = null; + + try{ + $this->connection = new PDO("mysql:host=" . $env_variables->getHost() . ";dbname=" . $env_variables->getNamedb(), + $env_variables->getUserDb(), $env_variables->getPasswordDb()); + }catch(PDOException $exception){ + echo "Connection failed: " . $exception->getMessage(); + } + + return $this->connection; + } +} ?> \ No newline at end of file diff --git a/config/environment_variables.php b/config/environment_variables.php new file mode 100644 index 0000000..c4672fe --- /dev/null +++ b/config/environment_variables.php @@ -0,0 +1,35 @@ + +DB_HOST; + } + + public function getNameDb() { + return $this->DB_NAME; + } + + public function getUserDb() { + return $this->DB_USER; + } + + public function getPasswordDb() { + return $this->DB_PASSWORD; + } + + public function getKeyJwt() { + return $this->KEY_JWT; + } + +} + +?> \ No newline at end of file From a4adea3261e6b1669a593c0d8af9e534a5583e89 Mon Sep 17 00:00:00 2001 From: jean Date: Mon, 25 Mar 2024 10:23:11 -0500 Subject: [PATCH 3/7] Implementacion registro de usuario con multicapa --- api-rest/.htaccess | 6 + README.md => api-rest/README.md | 0 {api => api-rest/api}/protected.php | 109 ++- api-rest/composer.json | 6 + composer.lock => api-rest/composer.lock | 73 +- .../config/EnvironmentVariables.php | 5 + api-rest/config/constants.php | 21 + api-rest/controllers/UserController.php | 28 + api-rest/data/DatabaseConexion.php | 39 + api-rest/data/DatabaseJsonResponse.php | 100 ++ api-rest/data/Queries.php | 24 + api-rest/index.php | 5 + api-rest/models/User.php | 51 ++ api-rest/routes/api.php | 47 + {vendor => api-rest/vendor}/autoload.php | 0 .../vendor}/composer/ClassLoader.php | 0 .../vendor}/composer/InstalledVersions.php | 0 {vendor => api-rest/vendor}/composer/LICENSE | 0 .../vendor}/composer/autoload_classmap.php | 0 api-rest/vendor/composer/autoload_files.php | 10 + .../vendor}/composer/autoload_namespaces.php | 0 .../vendor}/composer/autoload_psr4.php | 0 .../vendor}/composer/autoload_real.php | 12 + .../vendor}/composer/autoload_static.php | 4 + api-rest/vendor/composer/installed.json | 146 +++ api-rest/vendor/composer/installed.php | 47 + .../vendor}/composer/platform_check.php | 0 .../vendor}/firebase/php-jwt/CHANGELOG.md | 0 .../vendor}/firebase/php-jwt/LICENSE | 0 .../vendor}/firebase/php-jwt/README.md | 0 .../vendor}/firebase/php-jwt/composer.json | 0 .../php-jwt/src/BeforeValidException.php | 0 .../firebase/php-jwt/src/CachedKeySet.php | 0 .../firebase/php-jwt/src/ExpiredException.php | 0 .../vendor}/firebase/php-jwt/src/JWK.php | 0 .../vendor}/firebase/php-jwt/src/JWT.php | 0 .../src/JWTExceptionWithPayloadInterface.php | 0 .../vendor}/firebase/php-jwt/src/Key.php | 0 .../php-jwt/src/SignatureInvalidException.php | 0 .../flightphp/core/.vscode/settings.json | 5 + api-rest/vendor/flightphp/core/LICENSE | 21 + api-rest/vendor/flightphp/core/README.md | 56 ++ api-rest/vendor/flightphp/core/composer.json | 75 ++ .../vendor/flightphp/core/flight/Engine.php | 860 ++++++++++++++++++ .../vendor/flightphp/core/flight/Flight.php | 168 ++++ .../vendor/flightphp/core/flight/autoload.php | 10 + .../flightphp/core/flight/core/Dispatcher.php | 302 ++++++ .../flightphp/core/flight/core/Loader.php | 223 +++++ .../core/flight/database/PdoWrapper.php | 149 +++ .../flightphp/core/flight/net/Request.php | 417 +++++++++ .../flightphp/core/flight/net/Response.php | 433 +++++++++ .../flightphp/core/flight/net/Route.php | 258 ++++++ .../flightphp/core/flight/net/Router.php | 317 +++++++ .../flightphp/core/flight/template/View.php | 197 ++++ .../flightphp/core/flight/util/Collection.php | 253 ++++++ .../core/flight/util/ReturnTypeWillChange.php | 9 + api-rest/vendor/flightphp/core/index.php | 10 + api/login.php | 81 -- api/register.php | 59 -- composer.json | 5 - config/database.php | 24 - index.php | 0 vendor/composer/installed.json | 72 -- vendor/composer/installed.php | 32 - 64 files changed, 4440 insertions(+), 329 deletions(-) create mode 100644 api-rest/.htaccess rename README.md => api-rest/README.md (100%) rename {api => api-rest/api}/protected.php (92%) create mode 100644 api-rest/composer.json rename composer.lock => api-rest/composer.lock (50%) rename config/environment_variables.php => api-rest/config/EnvironmentVariables.php (79%) create mode 100644 api-rest/config/constants.php create mode 100644 api-rest/controllers/UserController.php create mode 100644 api-rest/data/DatabaseConexion.php create mode 100644 api-rest/data/DatabaseJsonResponse.php create mode 100644 api-rest/data/Queries.php create mode 100644 api-rest/index.php create mode 100644 api-rest/models/User.php create mode 100644 api-rest/routes/api.php rename {vendor => api-rest/vendor}/autoload.php (100%) rename {vendor => api-rest/vendor}/composer/ClassLoader.php (100%) rename {vendor => api-rest/vendor}/composer/InstalledVersions.php (100%) rename {vendor => api-rest/vendor}/composer/LICENSE (100%) rename {vendor => api-rest/vendor}/composer/autoload_classmap.php (100%) create mode 100644 api-rest/vendor/composer/autoload_files.php rename {vendor => api-rest/vendor}/composer/autoload_namespaces.php (100%) rename {vendor => api-rest/vendor}/composer/autoload_psr4.php (100%) rename {vendor => api-rest/vendor}/composer/autoload_real.php (68%) rename {vendor => api-rest/vendor}/composer/autoload_static.php (88%) create mode 100644 api-rest/vendor/composer/installed.json create mode 100644 api-rest/vendor/composer/installed.php rename {vendor => api-rest/vendor}/composer/platform_check.php (100%) rename {vendor => api-rest/vendor}/firebase/php-jwt/CHANGELOG.md (100%) rename {vendor => api-rest/vendor}/firebase/php-jwt/LICENSE (100%) rename {vendor => api-rest/vendor}/firebase/php-jwt/README.md (100%) rename {vendor => api-rest/vendor}/firebase/php-jwt/composer.json (100%) rename {vendor => api-rest/vendor}/firebase/php-jwt/src/BeforeValidException.php (100%) rename {vendor => api-rest/vendor}/firebase/php-jwt/src/CachedKeySet.php (100%) rename {vendor => api-rest/vendor}/firebase/php-jwt/src/ExpiredException.php (100%) rename {vendor => api-rest/vendor}/firebase/php-jwt/src/JWK.php (100%) rename {vendor => api-rest/vendor}/firebase/php-jwt/src/JWT.php (100%) rename {vendor => api-rest/vendor}/firebase/php-jwt/src/JWTExceptionWithPayloadInterface.php (100%) rename {vendor => api-rest/vendor}/firebase/php-jwt/src/Key.php (100%) rename {vendor => api-rest/vendor}/firebase/php-jwt/src/SignatureInvalidException.php (100%) create mode 100644 api-rest/vendor/flightphp/core/.vscode/settings.json create mode 100644 api-rest/vendor/flightphp/core/LICENSE create mode 100644 api-rest/vendor/flightphp/core/README.md create mode 100644 api-rest/vendor/flightphp/core/composer.json create mode 100644 api-rest/vendor/flightphp/core/flight/Engine.php create mode 100644 api-rest/vendor/flightphp/core/flight/Flight.php create mode 100644 api-rest/vendor/flightphp/core/flight/autoload.php create mode 100644 api-rest/vendor/flightphp/core/flight/core/Dispatcher.php create mode 100644 api-rest/vendor/flightphp/core/flight/core/Loader.php create mode 100644 api-rest/vendor/flightphp/core/flight/database/PdoWrapper.php create mode 100644 api-rest/vendor/flightphp/core/flight/net/Request.php create mode 100644 api-rest/vendor/flightphp/core/flight/net/Response.php create mode 100644 api-rest/vendor/flightphp/core/flight/net/Route.php create mode 100644 api-rest/vendor/flightphp/core/flight/net/Router.php create mode 100644 api-rest/vendor/flightphp/core/flight/template/View.php create mode 100644 api-rest/vendor/flightphp/core/flight/util/Collection.php create mode 100644 api-rest/vendor/flightphp/core/flight/util/ReturnTypeWillChange.php create mode 100644 api-rest/vendor/flightphp/core/index.php delete mode 100644 api/login.php delete mode 100644 api/register.php delete mode 100644 composer.json delete mode 100644 config/database.php delete mode 100644 index.php delete mode 100644 vendor/composer/installed.json delete mode 100644 vendor/composer/installed.php diff --git a/api-rest/.htaccess b/api-rest/.htaccess new file mode 100644 index 0000000..6f8f506 --- /dev/null +++ b/api-rest/.htaccess @@ -0,0 +1,6 @@ +RewriteEngine On +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule ^(.*)$ index.php [QSA,L] +RewriteEngine On +RewriteRule ^(.*)$ index.php \ No newline at end of file diff --git a/README.md b/api-rest/README.md similarity index 100% rename from README.md rename to api-rest/README.md diff --git a/api/protected.php b/api-rest/api/protected.php similarity index 92% rename from api/protected.php rename to api-rest/api/protected.php index 7ca9672..eca1d59 100644 --- a/api/protected.php +++ b/api-rest/api/protected.php @@ -1,56 +1,55 @@ -getConnection(); - -$data = json_decode(file_get_contents("php://input")); - - -$authHeader = $_SERVER['HTTP_AUTHORIZATION']; - -$arr = explode(" ", $authHeader); - - -echo json_encode(array( - "message" => "sd" .$arr[1] -)); - -$jwt = $arr[1]; - -if($jwt){ - - try { - - $decoded = JWT::decode($jwt, $secret_key, array('HS256')); - - // Access is granted. Add code of the operation here - - echo json_encode(array( - "message" => "Access granted:", - "error" => $e->getMessage() - )); - - }catch (Exception $e){ - - http_response_code(401); - - echo json_encode(array( - "message" => "Access denied.", - "error" => $e->getMessage() - )); -} - -} +getConnection(); + +$data = json_decode(file_get_contents("php://input")); + +$authHeader = $_SERVER['HTTP_AUTHORIZATION']; + +$arr = explode(" ", $authHeader); + + +echo json_encode(array( + "message" => "sd" .$arr[1] +)); + +$jwt = $arr[1]; + +if($jwt){ + + try { + + $decoded = JWT::decode($jwt, $secret_key, array('HS256')); + + // Access is granted. Add code of the operation here + + echo json_encode(array( + "message" => "Access granted:", + "error" => $e->getMessage() + )); + + }catch (Exception $e){ + + http_response_code(401); + + echo json_encode(array( + "message" => "Access denied.", + "error" => $e->getMessage() + )); +} + +} ?> \ No newline at end of file diff --git a/api-rest/composer.json b/api-rest/composer.json new file mode 100644 index 0000000..a69ed11 --- /dev/null +++ b/api-rest/composer.json @@ -0,0 +1,6 @@ +{ + "require": { + "firebase/php-jwt": "^6.10", + "flightphp/core": "^3.6" + } +} diff --git a/composer.lock b/api-rest/composer.lock similarity index 50% rename from composer.lock rename to api-rest/composer.lock index 9093509..2f35b10 100644 --- a/composer.lock +++ b/api-rest/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "dfe27548af6d83b7d428280eae1df087", + "content-hash": "1d5a1c71d9d057e75cf12606b25681a4", "packages": [ { "name": "firebase/php-jwt", @@ -68,6 +68,77 @@ "source": "https://github.com/firebase/php-jwt/tree/v6.10.0" }, "time": "2023-12-01T16:26:39+00:00" + }, + { + "name": "flightphp/core", + "version": "v3.6.2", + "source": { + "type": "git", + "url": "https://github.com/flightphp/core.git", + "reference": "412596e86335e4d55f337878a18bc45df9c7f987" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/flightphp/core/zipball/412596e86335e4d55f337878a18bc45df9c7f987", + "reference": "412596e86335e4d55f337878a18bc45df9c7f987", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.4|^8.0|^8.1|^8.2|^8.3" + }, + "replace": { + "mikecao/flight": "2.0.2" + }, + "require-dev": { + "ext-pdo_sqlite": "*", + "phpstan/extension-installer": "^1.3", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5", + "rregeer/phpunit-coverage-check": "^0.3.1", + "squizlabs/php_codesniffer": "^3.8" + }, + "suggest": { + "latte/latte": "Latte template engine", + "phpstan/phpstan": "PHP Static Analyzer", + "tracy/tracy": "Tracy debugger" + }, + "type": "library", + "autoload": { + "files": [ + "flight/autoload.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike Cao", + "email": "mike@mikecao.com", + "homepage": "http://www.mikecao.com/", + "role": "Original Developer" + }, + { + "name": "Franyer Sánchez", + "email": "franyeradriansanchez@gmail.com", + "homepage": "https://faslatam.000webhostapp.com", + "role": "Maintainer" + }, + { + "name": "n0nag0n", + "email": "n0nag0n@sky-9.com", + "role": "Maintainer" + } + ], + "description": "Flight is a fast, simple, extensible framework for PHP. Flight enables you to quickly and easily build RESTful web applications. This is the maintained fork of mikecao/flight", + "homepage": "http://flightphp.com", + "support": { + "issues": "https://github.com/flightphp/core/issues", + "source": "https://github.com/flightphp/core/tree/v3.6.2" + }, + "time": "2024-03-18T14:38:22+00:00" } ], "packages-dev": [], diff --git a/config/environment_variables.php b/api-rest/config/EnvironmentVariables.php similarity index 79% rename from config/environment_variables.php rename to api-rest/config/EnvironmentVariables.php index c4672fe..1310b7b 100644 --- a/config/environment_variables.php +++ b/api-rest/config/EnvironmentVariables.php @@ -9,6 +9,7 @@ class EnvironmentVariables { private $DB_PASSWORD = ""; private $KEY_JWT = "asdfghjkl_k1"; + private $ALGORITHM_JWT = "HS256"; public function getHost() { return $this->DB_HOST; @@ -30,6 +31,10 @@ public function getKeyJwt() { return $this->KEY_JWT; } + public function getAlgJwt() { + return $this->ALGORITHM_JWT; + } + } ?> \ No newline at end of file diff --git a/api-rest/config/constants.php b/api-rest/config/constants.php new file mode 100644 index 0000000..113ce89 --- /dev/null +++ b/api-rest/config/constants.php @@ -0,0 +1,21 @@ + \ No newline at end of file diff --git a/api-rest/controllers/UserController.php b/api-rest/controllers/UserController.php new file mode 100644 index 0000000..61eee43 --- /dev/null +++ b/api-rest/controllers/UserController.php @@ -0,0 +1,28 @@ +dbJsonResponse = new DatabaseJsonResponse(); + } + + public function login($email, $password) { + + return $this->dbJsonResponse->loginUser($email, $password); + + } + + public function registerUser($user) { + + return $this->dbJsonResponse->registerUser($user); + + } + +} + +?> \ No newline at end of file diff --git a/api-rest/data/DatabaseConexion.php b/api-rest/data/DatabaseConexion.php new file mode 100644 index 0000000..1bb3758 --- /dev/null +++ b/api-rest/data/DatabaseConexion.php @@ -0,0 +1,39 @@ +envVariables = new EnvironmentVariables(); + } + + // Método estático para obtener la instancia única de la clase + public static function getInstance() { + if (self::$instance == null) { + self::$instance = new DatabaseConexion(); + } + return self::$instance; + } + + // Retorna la conexion, si es nula la crea + public function getConnection() { + if ($this->connection == null) { + $this->connection = Flight::register('database', 'PDO', array( + 'mysql:host=' . $this->envVariables->getHost() . ';dbname=' . $this->envVariables->getNamedb(), + $this->envVariables->getUserDb(), + $this->envVariables->getPasswordDb() + )); + } + return $this->connection; + } +} + +?> diff --git a/api-rest/data/DatabaseJsonResponse.php b/api-rest/data/DatabaseJsonResponse.php new file mode 100644 index 0000000..07750d6 --- /dev/null +++ b/api-rest/data/DatabaseJsonResponse.php @@ -0,0 +1,100 @@ +conexion = DatabaseConexion::getInstance()->getConnection(); + $this->svDatabase = Flight::database(); // solicita el servicio de bd registrado en DatabaseConexion + $this->sqlQueries = new Queries(); + $this->envVariables = new EnvironmentVariables(); + } + + public function loginUser($email, $password) { + $query = $this->svDatabase->prepare($this->sqlQueries->queryLogin()); // Obtiene y prepara la consulta declarada en la clase Queries + $query->execute([":email_user" => $email]); // Ejecuta la consulta enviando el parametro necesario + $rowCount = $query->rowCount(); + + if ($rowCount != 0) { + $data = $query->fetch(); + $passw_db = $data['passw_user']; + if (password_verify($password, $passw_db)) { + + $token = array( + "iss" => ISS, + "aud" => AUD, + "iat" => IAT, + "nbf" => NBF, + "exp" => EXP, + + "data" => array( + "id" => $data['id_user'], + "nombre" => $data['nomb_user'], + "apellido" => $data['apell_user'], + "email" => $data['email_user'] + )); + + http_response_code(SUCCESS_RESPONSE); + + $jwt = JWT::encode($token, $this->envVariables->getKeyJwt(), $this->envVariables->getAlgJwt()); + return array( + "message" => "Inicio de sesión satisfactorio.", + "jwt" => $jwt, + "id" => $data['id_user'] + ); + + } else { + http_response_code(ACCESS_DENIED); + + return array("message" => "Inicio de sesión fallido."); + } + } + } + + public function registerUser($user) { + + $query = $this->svDatabase->prepare($this->sqlQueries->queryRegisterUser()); + + $array = [ + "error" => "Error el registrar usuario.", + "status" => "Error" + ]; + + $passw_hash = password_hash($user->getPasswUser(), PASSWORD_BCRYPT); + + $result = $query->execute([":nomb_user" => $user->getNombUser(), ":apell_user" => $user->getApellUser(), + ":nick_user" => $user->getNickUser(), ":email_user" => $user->getEmailUser(), + ":passw_user" => $passw_hash]); + + if ($result) { + + $array = [ + "User" => [ + "id" => $this->svDatabase->lastInsertId(), + "nombre" => $user->getNombUser(), + "apellido" => $user->getApellUser(), + "email" => $user->getEmailUser() + ], + "status" => "Registro satisfactorio." + ]; + } + + Flight::json($array); + + } +} + + +?> \ No newline at end of file diff --git a/api-rest/data/Queries.php b/api-rest/data/Queries.php new file mode 100644 index 0000000..d5447ef --- /dev/null +++ b/api-rest/data/Queries.php @@ -0,0 +1,24 @@ + \ No newline at end of file diff --git a/api-rest/index.php b/api-rest/index.php new file mode 100644 index 0000000..19d9b54 --- /dev/null +++ b/api-rest/index.php @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/api-rest/models/User.php b/api-rest/models/User.php new file mode 100644 index 0000000..bede468 --- /dev/null +++ b/api-rest/models/User.php @@ -0,0 +1,51 @@ +id_user = $id_user; + $this->nomb_user = $nomb_user; + $this->apell_user = $apell_user; + $this->nick_user = $nick_user; + $this->email_user = $email_user; + $this->passw_user = $passw_user; + + } + + public function getIdUser() { + return $this->id_user; + } + + public function getNombUser() { + return $this->nomb_user; + } + + public function getApellUser() { + return $this->apell_user; + } + + public function getNickUser() { + return $this->nick_user; + } + + public function getEmailUser() { + return $this->email_user; + } + + public function getPasswUser() { + return $this->passw_user; + } + +} + + +?> \ No newline at end of file diff --git a/api-rest/routes/api.php b/api-rest/routes/api.php new file mode 100644 index 0000000..0535720 --- /dev/null +++ b/api-rest/routes/api.php @@ -0,0 +1,47 @@ +data; // Obtener los datos JSON del cuerpo de la solicitud + + if (isset($data['email']) && isset($data['password'])) { + + Flight::json(getUserController()->login($data['email'], $data['password'])); + + } else { + + Flight::json(["error" => "Se requiere email y password"], BAD_REQUEST); + } +}); + +Flight::route('POST /registerUser', function () { + + $data = Flight::request()->data; + + if ($data != null) { + + $user = new User(0, $data['nombre'], $data['apellido'], + $data['nick'], $data['email'], $data['password']); + + Flight::json(getUserController()->registerUser($user)); + + } else { + + Flight::json(["error" => "Se requiere todos los campos", BAD_REQUEST]); + } + +}); + +Flight::start(); + +?> \ No newline at end of file diff --git a/vendor/autoload.php b/api-rest/vendor/autoload.php similarity index 100% rename from vendor/autoload.php rename to api-rest/vendor/autoload.php diff --git a/vendor/composer/ClassLoader.php b/api-rest/vendor/composer/ClassLoader.php similarity index 100% rename from vendor/composer/ClassLoader.php rename to api-rest/vendor/composer/ClassLoader.php diff --git a/vendor/composer/InstalledVersions.php b/api-rest/vendor/composer/InstalledVersions.php similarity index 100% rename from vendor/composer/InstalledVersions.php rename to api-rest/vendor/composer/InstalledVersions.php diff --git a/vendor/composer/LICENSE b/api-rest/vendor/composer/LICENSE similarity index 100% rename from vendor/composer/LICENSE rename to api-rest/vendor/composer/LICENSE diff --git a/vendor/composer/autoload_classmap.php b/api-rest/vendor/composer/autoload_classmap.php similarity index 100% rename from vendor/composer/autoload_classmap.php rename to api-rest/vendor/composer/autoload_classmap.php diff --git a/api-rest/vendor/composer/autoload_files.php b/api-rest/vendor/composer/autoload_files.php new file mode 100644 index 0000000..bf16bf2 --- /dev/null +++ b/api-rest/vendor/composer/autoload_files.php @@ -0,0 +1,10 @@ + $vendorDir . '/flightphp/core/flight/autoload.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/api-rest/vendor/composer/autoload_namespaces.php similarity index 100% rename from vendor/composer/autoload_namespaces.php rename to api-rest/vendor/composer/autoload_namespaces.php diff --git a/vendor/composer/autoload_psr4.php b/api-rest/vendor/composer/autoload_psr4.php similarity index 100% rename from vendor/composer/autoload_psr4.php rename to api-rest/vendor/composer/autoload_psr4.php diff --git a/vendor/composer/autoload_real.php b/api-rest/vendor/composer/autoload_real.php similarity index 68% rename from vendor/composer/autoload_real.php rename to api-rest/vendor/composer/autoload_real.php index 936cee1..59cb2c9 100644 --- a/vendor/composer/autoload_real.php +++ b/api-rest/vendor/composer/autoload_real.php @@ -33,6 +33,18 @@ public static function getLoader() $loader->register(true); + $filesToLoad = \Composer\Autoload\ComposerStaticInit20fad51902f91e7fd3039e016a6556b5::$files; + $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + + require $file; + } + }, null, null); + foreach ($filesToLoad as $fileIdentifier => $file) { + $requireFile($fileIdentifier, $file); + } + return $loader; } } diff --git a/vendor/composer/autoload_static.php b/api-rest/vendor/composer/autoload_static.php similarity index 88% rename from vendor/composer/autoload_static.php rename to api-rest/vendor/composer/autoload_static.php index db0c39b..20277c5 100644 --- a/vendor/composer/autoload_static.php +++ b/api-rest/vendor/composer/autoload_static.php @@ -6,6 +6,10 @@ class ComposerStaticInit20fad51902f91e7fd3039e016a6556b5 { + public static $files = array ( + '4cdafd4a5191caf078235e7dd119fdaf' => __DIR__ . '/..' . '/flightphp/core/flight/autoload.php', + ); + public static $prefixLengthsPsr4 = array ( 'F' => array ( diff --git a/api-rest/vendor/composer/installed.json b/api-rest/vendor/composer/installed.json new file mode 100644 index 0000000..184ff47 --- /dev/null +++ b/api-rest/vendor/composer/installed.json @@ -0,0 +1,146 @@ +{ + "packages": [ + { + "name": "firebase/php-jwt", + "version": "v6.10.0", + "version_normalized": "6.10.0.0", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "a49db6f0a5033aef5143295342f1c95521b075ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/a49db6f0a5033aef5143295342f1c95521b075ff", + "reference": "a49db6f0a5033aef5143295342f1c95521b075ff", + "shasum": "" + }, + "require": { + "php": "^7.4||^8.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^6.5||^7.4", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psr/cache": "^1.0||^2.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + }, + "suggest": { + "ext-sodium": "Support EdDSA (Ed25519) signatures", + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + }, + "time": "2023-12-01T16:26:39+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": [ + "jwt", + "php" + ], + "support": { + "issues": "https://github.com/firebase/php-jwt/issues", + "source": "https://github.com/firebase/php-jwt/tree/v6.10.0" + }, + "install-path": "../firebase/php-jwt" + }, + { + "name": "flightphp/core", + "version": "v3.6.2", + "version_normalized": "3.6.2.0", + "source": { + "type": "git", + "url": "https://github.com/flightphp/core.git", + "reference": "412596e86335e4d55f337878a18bc45df9c7f987" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/flightphp/core/zipball/412596e86335e4d55f337878a18bc45df9c7f987", + "reference": "412596e86335e4d55f337878a18bc45df9c7f987", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.4|^8.0|^8.1|^8.2|^8.3" + }, + "replace": { + "mikecao/flight": "2.0.2" + }, + "require-dev": { + "ext-pdo_sqlite": "*", + "phpstan/extension-installer": "^1.3", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5", + "rregeer/phpunit-coverage-check": "^0.3.1", + "squizlabs/php_codesniffer": "^3.8" + }, + "suggest": { + "latte/latte": "Latte template engine", + "phpstan/phpstan": "PHP Static Analyzer", + "tracy/tracy": "Tracy debugger" + }, + "time": "2024-03-18T14:38:22+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "flight/autoload.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike Cao", + "email": "mike@mikecao.com", + "homepage": "http://www.mikecao.com/", + "role": "Original Developer" + }, + { + "name": "Franyer Sánchez", + "email": "franyeradriansanchez@gmail.com", + "homepage": "https://faslatam.000webhostapp.com", + "role": "Maintainer" + }, + { + "name": "n0nag0n", + "email": "n0nag0n@sky-9.com", + "role": "Maintainer" + } + ], + "description": "Flight is a fast, simple, extensible framework for PHP. Flight enables you to quickly and easily build RESTful web applications. This is the maintained fork of mikecao/flight", + "homepage": "http://flightphp.com", + "support": { + "issues": "https://github.com/flightphp/core/issues", + "source": "https://github.com/flightphp/core/tree/v3.6.2" + }, + "install-path": "../flightphp/core" + } + ], + "dev": true, + "dev-package-names": [] +} diff --git a/api-rest/vendor/composer/installed.php b/api-rest/vendor/composer/installed.php new file mode 100644 index 0000000..c66dc31 --- /dev/null +++ b/api-rest/vendor/composer/installed.php @@ -0,0 +1,47 @@ + array( + 'name' => '__root__', + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'reference' => '9278279d38614d1f91097492db18b1a1ffb2276d', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => true, + ), + 'versions' => array( + '__root__' => array( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'reference' => '9278279d38614d1f91097492db18b1a1ffb2276d', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'firebase/php-jwt' => array( + 'pretty_version' => 'v6.10.0', + 'version' => '6.10.0.0', + 'reference' => 'a49db6f0a5033aef5143295342f1c95521b075ff', + 'type' => 'library', + 'install_path' => __DIR__ . '/../firebase/php-jwt', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'flightphp/core' => array( + 'pretty_version' => 'v3.6.2', + 'version' => '3.6.2.0', + 'reference' => '412596e86335e4d55f337878a18bc45df9c7f987', + 'type' => 'library', + 'install_path' => __DIR__ . '/../flightphp/core', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'mikecao/flight' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => '2.0.2', + ), + ), + ), +); diff --git a/vendor/composer/platform_check.php b/api-rest/vendor/composer/platform_check.php similarity index 100% rename from vendor/composer/platform_check.php rename to api-rest/vendor/composer/platform_check.php diff --git a/vendor/firebase/php-jwt/CHANGELOG.md b/api-rest/vendor/firebase/php-jwt/CHANGELOG.md similarity index 100% rename from vendor/firebase/php-jwt/CHANGELOG.md rename to api-rest/vendor/firebase/php-jwt/CHANGELOG.md diff --git a/vendor/firebase/php-jwt/LICENSE b/api-rest/vendor/firebase/php-jwt/LICENSE similarity index 100% rename from vendor/firebase/php-jwt/LICENSE rename to api-rest/vendor/firebase/php-jwt/LICENSE diff --git a/vendor/firebase/php-jwt/README.md b/api-rest/vendor/firebase/php-jwt/README.md similarity index 100% rename from vendor/firebase/php-jwt/README.md rename to api-rest/vendor/firebase/php-jwt/README.md diff --git a/vendor/firebase/php-jwt/composer.json b/api-rest/vendor/firebase/php-jwt/composer.json similarity index 100% rename from vendor/firebase/php-jwt/composer.json rename to api-rest/vendor/firebase/php-jwt/composer.json diff --git a/vendor/firebase/php-jwt/src/BeforeValidException.php b/api-rest/vendor/firebase/php-jwt/src/BeforeValidException.php similarity index 100% rename from vendor/firebase/php-jwt/src/BeforeValidException.php rename to api-rest/vendor/firebase/php-jwt/src/BeforeValidException.php diff --git a/vendor/firebase/php-jwt/src/CachedKeySet.php b/api-rest/vendor/firebase/php-jwt/src/CachedKeySet.php similarity index 100% rename from vendor/firebase/php-jwt/src/CachedKeySet.php rename to api-rest/vendor/firebase/php-jwt/src/CachedKeySet.php diff --git a/vendor/firebase/php-jwt/src/ExpiredException.php b/api-rest/vendor/firebase/php-jwt/src/ExpiredException.php similarity index 100% rename from vendor/firebase/php-jwt/src/ExpiredException.php rename to api-rest/vendor/firebase/php-jwt/src/ExpiredException.php diff --git a/vendor/firebase/php-jwt/src/JWK.php b/api-rest/vendor/firebase/php-jwt/src/JWK.php similarity index 100% rename from vendor/firebase/php-jwt/src/JWK.php rename to api-rest/vendor/firebase/php-jwt/src/JWK.php diff --git a/vendor/firebase/php-jwt/src/JWT.php b/api-rest/vendor/firebase/php-jwt/src/JWT.php similarity index 100% rename from vendor/firebase/php-jwt/src/JWT.php rename to api-rest/vendor/firebase/php-jwt/src/JWT.php diff --git a/vendor/firebase/php-jwt/src/JWTExceptionWithPayloadInterface.php b/api-rest/vendor/firebase/php-jwt/src/JWTExceptionWithPayloadInterface.php similarity index 100% rename from vendor/firebase/php-jwt/src/JWTExceptionWithPayloadInterface.php rename to api-rest/vendor/firebase/php-jwt/src/JWTExceptionWithPayloadInterface.php diff --git a/vendor/firebase/php-jwt/src/Key.php b/api-rest/vendor/firebase/php-jwt/src/Key.php similarity index 100% rename from vendor/firebase/php-jwt/src/Key.php rename to api-rest/vendor/firebase/php-jwt/src/Key.php diff --git a/vendor/firebase/php-jwt/src/SignatureInvalidException.php b/api-rest/vendor/firebase/php-jwt/src/SignatureInvalidException.php similarity index 100% rename from vendor/firebase/php-jwt/src/SignatureInvalidException.php rename to api-rest/vendor/firebase/php-jwt/src/SignatureInvalidException.php diff --git a/api-rest/vendor/flightphp/core/.vscode/settings.json b/api-rest/vendor/flightphp/core/.vscode/settings.json new file mode 100644 index 0000000..fcf56e7 --- /dev/null +++ b/api-rest/vendor/flightphp/core/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "php.suggest.basic": false, + "editor.detectIndentation": false, + "editor.insertSpaces": true +} diff --git a/api-rest/vendor/flightphp/core/LICENSE b/api-rest/vendor/flightphp/core/LICENSE new file mode 100644 index 0000000..cfbe851 --- /dev/null +++ b/api-rest/vendor/flightphp/core/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2011 Mike Cao + +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/api-rest/vendor/flightphp/core/README.md b/api-rest/vendor/flightphp/core/README.md new file mode 100644 index 0000000..4961468 --- /dev/null +++ b/api-rest/vendor/flightphp/core/README.md @@ -0,0 +1,56 @@ +![PHPStan: enabled](https://user-images.githubusercontent.com/104888/50957476-9c4acb80-14be-11e9-88ce-6447364dc1bb.png) +![PHPStan: level 6](https://img.shields.io/badge/PHPStan-level%206-brightgreen.svg?style=flat) +![Matrix](https://img.shields.io/matrix/flight-php-framework%3Amatrix.org?server_fqdn=matrix.org&style=social&logo=matrix) + +# What is Flight? + +Flight is a fast, simple, extensible framework for PHP. Flight enables you to +quickly and easily build RESTful web applications. + +# Basic Usage + +```php +// if installed with composer +require 'vendor/autoload.php'; +// or if installed manually by zip file +// require 'flight/Flight.php'; + +Flight::route('/', function () { + echo 'hello world!'; +}); + +Flight::start(); +``` + +## Skeleton App + +You can also install a skeleton app. Go to [flightphp/skeleton](https://github.com/flightphp/skeleton) for instructions on how to get started! + +# Documentation + +We have our own documentation website that is built with Flight (naturally). Learn more about the framework at [docs.flightphp.com](https://docs.flightphp.com). + +# Community + +Chat with us on Matrix IRC [#flight-php-framework:matrix.org](https://matrix.to/#/#flight-php-framework:matrix.org) + +# Upgrading From v2 + +If you have a current project on v2, you should be able to upgrade to v2 with no issues depending on how your project was built. If there are any issues with upgrade, they are documented in the [migrating to v3](https://docs.flightphp.com/learn/migrating-to-v3) documentation page. It is the intention of Flight to maintain longterm stability of the project and to not add rewrites with major version changes. + +# Requirements + +> [!IMPORTANT] +> Flight requires `PHP 7.4` or greater. + +**Note:** PHP 7.4 is supported because at the current time of writing (2024) PHP 7.4 is the default version for some LTS Linux distributions. Forcing a move to PHP >8 would cause a lot of heartburn for those users. + +The framework also supports PHP >8. + +# Roadmap + +To see the current and future roadmap for the Flight Framework, visit the [project roadmap](https://github.com/orgs/flightphp/projects/1/views/1) + +# License + +Flight is released under the [MIT](http://docs.flightphp.com/license) license. diff --git a/api-rest/vendor/flightphp/core/composer.json b/api-rest/vendor/flightphp/core/composer.json new file mode 100644 index 0000000..028f2da --- /dev/null +++ b/api-rest/vendor/flightphp/core/composer.json @@ -0,0 +1,75 @@ +{ + "name": "flightphp/core", + "description": "Flight is a fast, simple, extensible framework for PHP. Flight enables you to quickly and easily build RESTful web applications. This is the maintained fork of mikecao/flight", + "homepage": "http://flightphp.com", + "license": "MIT", + "authors": [ + { + "name": "Mike Cao", + "email": "mike@mikecao.com", + "homepage": "http://www.mikecao.com/", + "role": "Original Developer" + }, + { + "name": "Franyer Sánchez", + "email": "franyeradriansanchez@gmail.com", + "homepage": "https://faslatam.000webhostapp.com", + "role": "Maintainer" + }, + { + "name": "n0nag0n", + "email": "n0nag0n@sky-9.com", + "role": "Maintainer" + } + ], + "require": { + "php": "^7.4|^8.0|^8.1|^8.2|^8.3", + "ext-json": "*" + }, + "autoload": { + "files": [ + "flight/autoload.php" + ] + }, + "autoload-dev": { + "classmap": [ + "tests/classes/User.php", + "tests/classes/Hello.php", + "tests/classes/Factory.php", + "tests/classes/TesterClass.php" + ] + }, + "require-dev": { + "ext-pdo_sqlite": "*", + "phpunit/phpunit": "^9.5", + "phpstan/phpstan": "^1.10", + "phpstan/extension-installer": "^1.3", + "rregeer/phpunit-coverage-check": "^0.3.1", + "squizlabs/php_codesniffer": "^3.8" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true + }, + "process-timeout": 0, + "sort-packages": true + }, + "scripts": { + "test": "phpunit", + "test-coverage": "rm -f clover.xml && XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html=coverage --coverage-clover=clover.xml && vendor/bin/coverage-check clover.xml 100", + "test-server": "echo \"Running Test Server\" && php -S localhost:8000 -t tests/server/", + "test-server-v2": "echo \"Running Test Server\" && php -S localhost:8000 -t tests/server-v2/", + "test-coverage:win": "del clover.xml && phpunit --coverage-html=coverage --coverage-clover=clover.xml && coverage-check clover.xml 100", + "lint": "phpstan --no-progress -cphpstan.neon", + "beautify": "phpcbf --standard=phpcs.xml", + "phpcs": "phpcs --standard=phpcs.xml -n" + }, + "suggest": { + "latte/latte": "Latte template engine", + "tracy/tracy": "Tracy debugger", + "phpstan/phpstan": "PHP Static Analyzer" + }, + "replace": { + "mikecao/flight": "2.0.2" + } +} diff --git a/api-rest/vendor/flightphp/core/flight/Engine.php b/api-rest/vendor/flightphp/core/flight/Engine.php new file mode 100644 index 0000000..effb0af --- /dev/null +++ b/api-rest/vendor/flightphp/core/flight/Engine.php @@ -0,0 +1,860 @@ + + * + * # Core methods + * @method void start() Starts engine + * @method void stop() Stops framework and outputs current response + * @method void halt(int $code = 200, string $message = '', bool $actuallyExit = true) Stops processing and returns a given response. + * + * # Routing + * @method Route route(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') + * Routes a URL to a callback function with all applicable methods + * @method void group(string $pattern, callable $callback, array $group_middlewares = []) + * Groups a set of routes together under a common prefix. + * @method Route post(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') + * Routes a POST URL to a callback function. + * @method Route put(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') + * Routes a PUT URL to a callback function. + * @method Route patch(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') + * Routes a PATCH URL to a callback function. + * @method Route delete(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') + * Routes a DELETE URL to a callback function. + * @method Router router() Gets router + * @method string getUrl(string $alias) Gets a url from an alias + * + * # Views + * @method void render(string $file, ?array $data = null, ?string $key = null) Renders template + * @method View view() Gets current view + * + * # Request-Response + * @method Request request() Gets current request + * @method Response response() Gets current response + * @method void error(Throwable $e) Sends an HTTP 500 response for any errors. + * @method void notFound() Sends an HTTP 404 response when a URL is not found. + * @method void redirect(string $url, int $code = 303) Redirects the current request to another URL. + * @method void json(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0) + * Sends a JSON response. + * @method void jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0) + * Sends a JSONP response. + * + * # HTTP caching + * @method void etag(string $id, ('strong'|'weak') $type = 'strong') Handles ETag HTTP caching. + * @method void lastModified(int $time) Handles last modified HTTP caching. + * + * phpcs:disable PSR2.Methods.MethodDeclaration.Underscore + */ +class Engine +{ + /** + * @var array List of methods that can be extended in the Engine class. + */ + private const MAPPABLE_METHODS = [ + 'start', 'stop', 'route', 'halt', 'error', 'notFound', + 'render', 'redirect', 'etag', 'lastModified', 'json', 'jsonp', + 'post', 'put', 'patch', 'delete', 'group', 'getUrl' + ]; + + /** @var array Stored variables. */ + protected array $vars = []; + + /** Class loader. */ + protected Loader $loader; + + /** Event dispatcher. */ + protected Dispatcher $dispatcher; + + /** If the framework has been initialized or not. */ + protected bool $initialized = false; + + public function __construct() + { + $this->loader = new Loader(); + $this->dispatcher = new Dispatcher(); + $this->init(); + } + + /** + * Handles calls to class methods. + * + * @param string $name Method name + * @param array $params Method parameters + * + * @throws Exception + * @return mixed Callback results + */ + public function __call(string $name, array $params) + { + $callback = $this->dispatcher->get($name); + + if (\is_callable($callback)) { + return $this->dispatcher->run($name, $params); + } + + if (!$this->loader->get($name)) { + throw new Exception("$name must be a mapped method."); + } + + $shared = empty($params) || $params[0]; + + return $this->loader->load($name, $shared); + } + + ////////////////// + // Core Methods // + ////////////////// + + /** Initializes the framework. */ + public function init(): void + { + $initialized = $this->initialized; + $self = $this; + + if ($initialized) { + $this->vars = []; + $this->loader->reset(); + $this->dispatcher->reset(); + } + + // Register default components + $this->loader->register('request', Request::class); + $this->loader->register('response', Response::class); + $this->loader->register('router', Router::class); + + $this->loader->register('view', View::class, [], function (View $view) use ($self) { + $view->path = $self->get('flight.views.path'); + $view->extension = $self->get('flight.views.extension'); + }); + + foreach (self::MAPPABLE_METHODS as $name) { + $this->dispatcher->set($name, [$this, "_$name"]); + } + + // Default configuration settings + $this->set('flight.base_url'); + $this->set('flight.case_sensitive', false); + $this->set('flight.handle_errors', true); + $this->set('flight.log_errors', false); + $this->set('flight.views.path', './views'); + $this->set('flight.views.extension', '.php'); + $this->set('flight.content_length', true); + $this->set('flight.v2.output_buffering', false); + + // Startup configuration + $this->before('start', function () use ($self) { + // Enable error handling + if ($self->get('flight.handle_errors')) { + set_error_handler([$self, 'handleError']); + set_exception_handler([$self, 'handleException']); + } + + // Set case-sensitivity + $self->router()->case_sensitive = $self->get('flight.case_sensitive'); + // Set Content-Length + $self->response()->content_length = $self->get('flight.content_length'); + // This is to maintain legacy handling of output buffering + // which causes a lot of problems. This will be removed + // in v4 + $self->response()->v2_output_buffering = $this->get('flight.v2.output_buffering'); + }); + + $this->initialized = true; + } + + /** + * Custom error handler. Converts errors into exceptions. + * + * @param int $errno Error number + * @param string $errstr Error string + * @param string $errfile Error file name + * @param int $errline Error file line number + * + * @return false + * @throws ErrorException + */ + public function handleError(int $errno, string $errstr, string $errfile, int $errline): bool + { + if ($errno & error_reporting()) { + throw new ErrorException($errstr, $errno, 0, $errfile, $errline); + } + + return false; + } + + /** + * Custom exception handler. Logs exceptions. + * + * @param Throwable $e Thrown exception + */ + public function handleException(Throwable $e): void + { + if ($this->get('flight.log_errors')) { + error_log($e->getMessage()); // @codeCoverageIgnore + } + + $this->error($e); + } + + /** + * Maps a callback to a framework method. + * + * @param string $name Method name + * @param callable $callback Callback function + * + * @throws Exception If trying to map over a framework method + */ + public function map(string $name, callable $callback): void + { + if (method_exists($this, $name)) { + throw new Exception('Cannot override an existing framework method.'); + } + + $this->dispatcher->set($name, $callback); + } + + /** + * Registers a class to a framework method. + * + * # Usage example: + * ``` + * $app = new Engine; + * $app->register('user', User::class); + * + * $app->user(); # <- Return a User instance + * ``` + * + * @param string $name Method name + * @param class-string $class Class name + * @param array $params Class initialization parameters + * @param ?Closure(T $instance): void $callback Function to call after object instantiation + * + * @template T of object + * @throws Exception If trying to map over a framework method + */ + public function register(string $name, string $class, array $params = [], ?callable $callback = null): void + { + if (method_exists($this, $name)) { + throw new Exception('Cannot override an existing framework method.'); + } + + $this->loader->register($name, $class, $params, $callback); + } + + /** Unregisters a class to a framework method. */ + public function unregister(string $methodName): void + { + $this->loader->unregister($methodName); + } + + /** + * Adds a pre-filter to a method. + * + * @param string $name Method name + * @param Closure(array &$params, string &$output): (void|false) $callback + */ + public function before(string $name, callable $callback): void + { + $this->dispatcher->hook($name, 'before', $callback); + } + + /** + * Adds a post-filter to a method. + * + * @param string $name Method name + * @param Closure(array &$params, string &$output): (void|false) $callback + */ + public function after(string $name, callable $callback): void + { + $this->dispatcher->hook($name, 'after', $callback); + } + + /** + * Gets a variable. + * + * @param ?string $key Variable name + * + * @return mixed Variable value or `null` if `$key` doesn't exists. + */ + public function get(?string $key = null) + { + if (null === $key) { + return $this->vars; + } + + return $this->vars[$key] ?? null; + } + + /** + * Sets a variable. + * + * @param string|iterable $key + * Variable name as `string` or an iterable of `'varName' => $varValue` + * @param mixed $value Ignored if `$key` is an `iterable` + */ + public function set($key, $value = null): void + { + if (\is_iterable($key)) { + foreach ($key as $k => $v) { + $this->vars[$k] = $v; + } + + return; + } + + $this->vars[$key] = $value; + } + + /** + * Checks if a variable has been set. + * + * @param string $key Variable name + * + * @return bool Variable status + */ + public function has(string $key): bool + { + return isset($this->vars[$key]); + } + + /** + * Unsets a variable. If no key is passed in, clear all variables. + * + * @param ?string $key Variable name, if `$key` isn't provided, it clear all variables. + */ + public function clear(?string $key = null): void + { + if (null === $key) { + $this->vars = []; + return; + } + + unset($this->vars[$key]); + } + + /** + * Adds a path for class autoloading. + * + * @param string $dir Directory path + */ + public function path(string $dir): void + { + $this->loader->addDirectory($dir); + } + + /** + * Processes each routes middleware. + * + * @param Route $route The route to process the middleware for. + * @param string $event_name If this is the before or after method. + */ + protected function processMiddleware(Route $route, string $event_name): bool + { + $at_least_one_middleware_failed = false; + + $middlewares = $event_name === Dispatcher::FILTER_BEFORE ? $route->middleware : array_reverse($route->middleware); + $params = $route->params; + + foreach ($middlewares as $middleware) { + $middleware_object = false; + + if ($event_name === Dispatcher::FILTER_BEFORE) { + // can be a callable or a class + $middleware_object = (is_callable($middleware) === true + ? $middleware + : (method_exists($middleware, Dispatcher::FILTER_BEFORE) === true + ? [$middleware, Dispatcher::FILTER_BEFORE] + : false + ) + ); + } elseif ($event_name === Dispatcher::FILTER_AFTER) { + // must be an object. No functions allowed here + if ( + is_object($middleware) === true + && !($middleware instanceof Closure) + && method_exists($middleware, Dispatcher::FILTER_AFTER) === true + ) { + $middleware_object = [$middleware, Dispatcher::FILTER_AFTER]; + } + } + + if ($middleware_object === false) { + continue; + } + + $use_v3_output_buffering = + $this->response()->v2_output_buffering === false && + $route->is_streamed === false; + + if ($use_v3_output_buffering === true) { + ob_start(); + } + + // It's assumed if you don't declare before, that it will be assumed as the before method + $middleware_result = $middleware_object($params); + + if ($use_v3_output_buffering === true) { + $this->response()->write(ob_get_clean()); + } + + if ($middleware_result === false) { + $at_least_one_middleware_failed = true; + break; + } + } + + return $at_least_one_middleware_failed; + } + + //////////////////////// + // Extensible Methods // + //////////////////////// + /** + * Starts the framework. + * + * @throws Exception + */ + public function _start(): void + { + $dispatched = false; + $self = $this; + $request = $this->request(); + $response = $this->response(); + $router = $this->router(); + + // Allow filters to run + $this->after('start', function () use ($self) { + $self->stop(); + }); + + if ($response->v2_output_buffering === true) { + // Flush any existing output + if (ob_get_length() > 0) { + $response->write(ob_get_clean()); // @codeCoverageIgnore + } + + // Enable output buffering + // This is closed in the Engine->_stop() method + ob_start(); + } + + // Route the request + $failed_middleware_check = false; + + while ($route = $router->route($request)) { + $params = array_values($route->params); + + // Add route info to the parameter list + if ($route->pass) { + $params[] = $route; + } + + // If this route is to be streamed, we need to output the headers now + if ($route->is_streamed === true) { + $response->status($route->streamed_headers['status']); + unset($route->streamed_headers['status']); + $response->header('X-Accel-Buffering', 'no'); + $response->header('Connection', 'close'); + foreach ($route->streamed_headers as $header => $value) { + $response->header($header, $value); + } + + // We obviously don't know the content length right now. This must be false. + $response->content_length = false; + $response->sendHeaders(); + $response->markAsSent(); + } + + // Run any before middlewares + if (count($route->middleware) > 0) { + $at_least_one_middleware_failed = $this->processMiddleware($route, 'before'); + if ($at_least_one_middleware_failed === true) { + $failed_middleware_check = true; + break; + } + } + + $use_v3_output_buffering = + $this->response()->v2_output_buffering === false && + $route->is_streamed === false; + + if ($use_v3_output_buffering === true) { + ob_start(); + } + + // Call route handler + $continue = $this->dispatcher->execute( + $route->callback, + $params + ); + + if ($use_v3_output_buffering === true) { + $response->write(ob_get_clean()); + } + + // Run any before middlewares + if (count($route->middleware) > 0) { + // process the middleware in reverse order now + $at_least_one_middleware_failed = $this->processMiddleware($route, 'after'); + + if ($at_least_one_middleware_failed === true) { + $failed_middleware_check = true; + break; + } + } + + $dispatched = true; + + if (!$continue) { + break; + } + + $router->next(); + + $dispatched = false; + } + + // HEAD requests should be identical to GET requests but have no body + if ($request->method === 'HEAD') { + $response->clearBody(); + } + + if ($failed_middleware_check === true) { + $this->halt(403, 'Forbidden', empty(getenv('PHPUNIT_TEST'))); + } elseif ($dispatched === false) { + $this->notFound(); + } + } + + /** + * Sends an HTTP 500 response for any errors. + * + * @param Throwable $e Thrown exception + */ + public function _error(Throwable $e): void + { + $msg = sprintf( + '

500 Internal Server Error

' . + '

%s (%s)

' . + '
%s
', + $e->getMessage(), + $e->getCode(), + $e->getTraceAsString() + ); + + try { + $this->response() + ->clear() + ->status(500) + ->write($msg) + ->send(); + // @codeCoverageIgnoreStart + } catch (Throwable $t) { + exit($msg); + } + // @codeCoverageIgnoreEnd + } + + /** + * Stops the framework and outputs the current response. + * + * @param ?int $code HTTP status code + * + * @throws Exception + * @deprecated 3.5.3 This method will be removed in v4 + */ + public function _stop(?int $code = null): void + { + $response = $this->response(); + + if (!$response->sent()) { + if (null !== $code) { + $response->status($code); + } + + if ($response->v2_output_buffering === true && ob_get_length() > 0) { + $response->write(ob_get_clean()); + } + + $response->send(); + } + } + + /** + * Routes a URL to a callback function. + * + * @param string $pattern URL pattern to match + * @param callable $callback Callback function + * @param bool $pass_route Pass the matching route object to the callback + * @param string $alias The alias for the route + */ + public function _route(string $pattern, callable $callback, bool $pass_route = false, string $alias = ''): Route + { + return $this->router()->map($pattern, $callback, $pass_route, $alias); + } + + /** + * Routes a URL to a callback function. + * + * @param string $pattern URL pattern to match + * @param callable $callback Callback function that includes the Router class as first parameter + * @param array $group_middlewares The middleware to be applied to the route + */ + public function _group(string $pattern, callable $callback, array $group_middlewares = []): void + { + $this->router()->group($pattern, $callback, $group_middlewares); + } + + /** + * Routes a URL to a callback function. + * + * @param string $pattern URL pattern to match + * @param callable $callback Callback function + * @param bool $pass_route Pass the matching route object to the callback + */ + public function _post(string $pattern, callable $callback, bool $pass_route = false, string $route_alias = ''): void + { + $this->router()->map('POST ' . $pattern, $callback, $pass_route, $route_alias); + } + + /** + * Routes a URL to a callback function. + * + * @param string $pattern URL pattern to match + * @param callable $callback Callback function + * @param bool $pass_route Pass the matching route object to the callback + */ + public function _put(string $pattern, callable $callback, bool $pass_route = false, string $route_alias = ''): void + { + $this->router()->map('PUT ' . $pattern, $callback, $pass_route, $route_alias); + } + + /** + * Routes a URL to a callback function. + * + * @param string $pattern URL pattern to match + * @param callable $callback Callback function + * @param bool $pass_route Pass the matching route object to the callback + */ + public function _patch(string $pattern, callable $callback, bool $pass_route = false, string $route_alias = ''): void + { + $this->router()->map('PATCH ' . $pattern, $callback, $pass_route, $route_alias); + } + + /** + * Routes a URL to a callback function. + * + * @param string $pattern URL pattern to match + * @param callable $callback Callback function + * @param bool $pass_route Pass the matching route object to the callback + */ + public function _delete(string $pattern, callable $callback, bool $pass_route = false, string $route_alias = ''): void + { + $this->router()->map('DELETE ' . $pattern, $callback, $pass_route, $route_alias); + } + + /** + * Stops processing and returns a given response. + * + * @param int $code HTTP status code + * @param string $message Response message + * @param bool $actuallyExit Whether to actually exit the script or just send response + */ + public function _halt(int $code = 200, string $message = '', bool $actuallyExit = true): void + { + $this->response() + ->clear() + ->status($code) + ->write($message) + ->send(); + if ($actuallyExit === true) { + exit(); // @codeCoverageIgnore + } + } + + /** Sends an HTTP 404 response when a URL is not found. */ + public function _notFound(): void + { + $output = '

404 Not Found

The page you have requested could not be found.

'; + + $this->response() + ->clear() + ->status(404) + ->write($output) + ->send(); + } + + /** + * Redirects the current request to another URL. + * + * @param int $code HTTP status code + */ + public function _redirect(string $url, int $code = 303): void + { + $base = $this->get('flight.base_url'); + + if (null === $base) { + $base = $this->request()->base; + } + + // Append base url to redirect url + if ('/' !== $base && false === strpos($url, '://')) { + $url = $base . preg_replace('#/+#', '/', '/' . $url); + } + + $this->response() + ->clear() + ->status($code) + ->header('Location', $url) + ->send(); + } + + /** + * Renders a template. + * + * @param string $file Template file + * @param ?array $data Template data + * @param ?string $key View variable name + * + * @throws Exception If template file wasn't found + */ + public function _render(string $file, ?array $data = null, ?string $key = null): void + { + if (null !== $key) { + $this->view()->set($key, $this->view()->fetch($file, $data)); + return; + } + + $this->view()->render($file, $data); + } + + /** + * Sends a JSON response. + * + * @param mixed $data JSON data + * @param int $code HTTP status code + * @param bool $encode Whether to perform JSON encoding + * @param string $charset Charset + * @param int $option Bitmask Json constant such as JSON_HEX_QUOT + * + * @throws Exception + */ + public function _json( + $data, + int $code = 200, + bool $encode = true, + string $charset = 'utf-8', + int $option = 0 + ): void { + $json = $encode ? json_encode($data, $option) : $data; + + $this->response() + ->status($code) + ->header('Content-Type', 'application/json; charset=' . $charset) + ->write($json); + if ($this->response()->v2_output_buffering === true) { + $this->response()->send(); + } + } + + /** + * Sends a JSONP response. + * + * @param mixed $data JSON data + * @param string $param Query parameter that specifies the callback name. + * @param int $code HTTP status code + * @param bool $encode Whether to perform JSON encoding + * @param string $charset Charset + * @param int $option Bitmask Json constant such as JSON_HEX_QUOT + * + * @throws Exception + */ + public function _jsonp( + $data, + string $param = 'jsonp', + int $code = 200, + bool $encode = true, + string $charset = 'utf-8', + int $option = 0 + ): void { + $json = $encode ? json_encode($data, $option) : $data; + $callback = $this->request()->query[$param]; + + $this->response() + ->status($code) + ->header('Content-Type', 'application/javascript; charset=' . $charset) + ->write($callback . '(' . $json . ');'); + if ($this->response()->v2_output_buffering === true) { + $this->response()->send(); + } + } + + /** + * Handles ETag HTTP caching. + * + * @param string $id ETag identifier + * @param 'strong'|'weak' $type ETag type + */ + public function _etag(string $id, string $type = 'strong'): void + { + $id = (('weak' === $type) ? 'W/' : '') . $id; + + $this->response()->header('ETag', '"' . str_replace('"', '\"', $id) . '"'); + + if ( + isset($_SERVER['HTTP_IF_NONE_MATCH']) && + $_SERVER['HTTP_IF_NONE_MATCH'] === $id + ) { + $this->halt(304, '', empty(getenv('PHPUNIT_TEST'))); + } + } + + /** + * Handles last modified HTTP caching. + * + * @param int $time Unix timestamp + */ + public function _lastModified(int $time): void + { + $this->response()->header('Last-Modified', gmdate('D, d M Y H:i:s \G\M\T', $time)); + + if ( + isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && + strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) === $time + ) { + $this->halt(304, '', empty(getenv('PHPUNIT_TEST'))); + } + } + + /** + * Gets a url from an alias that's supplied. + * + * @param string $alias the route alias. + * @param array $params The params for the route if applicable. + */ + public function _getUrl(string $alias, array $params = []): string + { + return $this->router()->getUrlByAlias($alias, $params); + } +} diff --git a/api-rest/vendor/flightphp/core/flight/Flight.php b/api-rest/vendor/flightphp/core/flight/Flight.php new file mode 100644 index 0000000..6e29781 --- /dev/null +++ b/api-rest/vendor/flightphp/core/flight/Flight.php @@ -0,0 +1,168 @@ + + * + * # Core methods + * @method static void start() Starts the framework. + * @method static void path(string $path) Adds a path for autoloading classes. + * @method static void stop(?int $code = null) Stops the framework and sends a response. + * @method static void halt(int $code = 200, string $message = '', bool $actuallyExit = true) + * Stop the framework with an optional status code and message. + * + * # Routing + * @method static Route route(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') + * Maps a URL pattern to a callback with all applicable methods. + * @method static void group(string $pattern, callable $callback, callable[] $group_middlewares = []) + * Groups a set of routes together under a common prefix. + * @method static Route post(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') + * Routes a POST URL to a callback function. + * @method static Route put(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') + * Routes a PUT URL to a callback function. + * @method static Route patch(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') + * Routes a PATCH URL to a callback function. + * @method static Route delete(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') + * Routes a DELETE URL to a callback function. + * @method static Router router() Returns Router instance. + * @method static string getUrl(string $alias, array $params = []) Gets a url from an alias + * + * @method static void map(string $name, callable $callback) Creates a custom framework method. + * + * @method static void before(string $name, Closure(array &$params, string &$output): (void|false) $callback) + * Adds a filter before a framework method. + * @method static void after(string $name, Closure(array &$params, string &$output): (void|false) $callback) + * Adds a filter after a framework method. + * + * @method static void set(string|iterable $key, mixed $value) Sets a variable. + * @method static mixed get(?string $key) Gets a variable. + * @method static bool has(string $key) Checks if a variable is set. + * @method static void clear(?string $key = null) Clears a variable. + * + * # Views + * @method static void render(string $file, ?array $data = null, ?string $key = null) + * Renders a template file. + * @method static View view() Returns View instance. + * + * # Request-Response + * @method static Request request() Returns Request instance. + * @method static Response response() Returns Response instance. + * @method static void redirect(string $url, int $code = 303) Redirects to another URL. + * @method static void json(mixed $data, int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512) + * Sends a JSON response. + * @method static void jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512) + * Sends a JSONP response. + * @method static void error(Throwable $exception) Sends an HTTP 500 response. + * @method static void notFound() Sends an HTTP 404 response. + * + * # HTTP caching + * @method static void etag(string $id, ('strong'|'weak') $type = 'strong') Performs ETag HTTP caching. + * @method static void lastModified(int $time) Performs last modified HTTP caching. + */ +class Flight +{ + /** Framework engine. */ + private static Engine $engine; + + /** Whether or not the app has been initialized. */ + private static bool $initialized = false; + + /** + * Don't allow object instantiation + * + * @codeCoverageIgnore + * @return void + */ + private function __construct() + { + } + + /** + * Forbid cloning the class + * + * @codeCoverageIgnore + * @return void + */ + private function __clone() + { + } + + /** + * Registers a class to a framework method. + * + * # Usage example: + * ``` + * Flight::register('user', User::class); + * + * Flight::user(); # <- Return a User instance + * ``` + * + * @param string $name Static method name + * @param class-string $class Fully Qualified Class Name + * @param array $params Class constructor params + * @param ?Closure(T $instance): void $callback Perform actions with the instance + * + * @template T of object + */ + public static function register($name, $class, $params = [], $callback = null): void + { + static::__callStatic('register', [$name, $class, $params, $callback]); + } + + /** Unregisters a class. */ + public static function unregister(string $methodName): void + { + static::__callStatic('unregister', [$methodName]); + } + + /** + * Handles calls to static methods. + * + * @param string $name Method name + * @param array $params Method parameters + * + * @return mixed Callback results + * @throws Exception + */ + public static function __callStatic(string $name, array $params) + { + return Dispatcher::invokeMethod([self::app(), $name], $params); + } + + /** @return Engine Application instance */ + public static function app(): Engine + { + if (!self::$initialized) { + require_once __DIR__ . '/autoload.php'; + + self::setEngine(new Engine()); + self::$initialized = true; + } + + return self::$engine; + } + + /** + * Set the engine instance + * + * @param Engine $engine Vroom vroom! + */ + public static function setEngine(Engine $engine): void + { + self::$engine = $engine; + } +} diff --git a/api-rest/vendor/flightphp/core/flight/autoload.php b/api-rest/vendor/flightphp/core/flight/autoload.php new file mode 100644 index 0000000..0a31c86 --- /dev/null +++ b/api-rest/vendor/flightphp/core/flight/autoload.php @@ -0,0 +1,10 @@ + + */ +class Dispatcher +{ + public const FILTER_BEFORE = 'before'; + public const FILTER_AFTER = 'after'; + private const FILTER_TYPES = [self::FILTER_BEFORE, self::FILTER_AFTER]; + + /** @var array Mapped events. */ + protected array $events = []; + + /** + * Method filters. + * + * @var array &$params, mixed &$output): (void|false)>>> + */ + protected array $filters = []; + + /** + * Dispatches an event. + * + * @param string $name Event name + * @param array $params Callback parameters. + * + * @return mixed Output of callback + * @throws Exception If event name isn't found or if event throws an `Exception` + */ + public function run(string $name, array $params = []) + { + $this->runPreFilters($name, $params); + $output = $this->runEvent($name, $params); + + return $this->runPostFilters($name, $output); + } + + /** + * @param array &$params + * + * @return $this + * @throws Exception + */ + protected function runPreFilters(string $eventName, array &$params): self + { + $thereAreBeforeFilters = !empty($this->filters[$eventName][self::FILTER_BEFORE]); + + if ($thereAreBeforeFilters) { + $this->filter($this->filters[$eventName][self::FILTER_BEFORE], $params, $output); + } + + return $this; + } + + /** + * @param array &$params + * + * @return void|mixed + * @throws Exception + */ + protected function runEvent(string $eventName, array &$params) + { + $requestedMethod = $this->get($eventName); + + if ($requestedMethod === null) { + throw new Exception("Event '$eventName' isn't found."); + } + + return $requestedMethod(...$params); + } + + /** + * @param mixed &$output + * + * @return mixed + * @throws Exception + */ + protected function runPostFilters(string $eventName, &$output) + { + static $params = []; + + $thereAreAfterFilters = !empty($this->filters[$eventName][self::FILTER_AFTER]); + + if ($thereAreAfterFilters) { + $this->filter($this->filters[$eventName][self::FILTER_AFTER], $params, $output); + } + + return $output; + } + + /** + * Assigns a callback to an event. + * + * @param string $name Event name + * @param Closure(): (void|mixed) $callback Callback function + * + * @return $this + */ + public function set(string $name, callable $callback): self + { + $this->events[$name] = $callback; + + return $this; + } + + /** + * Gets an assigned callback. + * + * @param string $name Event name + * + * @return null|(Closure(): (void|mixed)) $callback Callback function + */ + public function get(string $name): ?callable + { + return $this->events[$name] ?? null; + } + + /** + * Checks if an event has been set. + * + * @param string $name Event name + * + * @return bool Event status + */ + public function has(string $name): bool + { + return isset($this->events[$name]); + } + + /** + * Clears an event. If no name is given, all events will be removed. + * + * @param ?string $name Event name + */ + public function clear(?string $name = null): void + { + if ($name !== null) { + unset($this->events[$name]); + unset($this->filters[$name]); + + return; + } + + $this->events = []; + $this->filters = []; + } + + /** + * Hooks a callback to an event. + * + * @param string $name Event name + * @param 'before'|'after' $type Filter type + * @param Closure(array &$params, string &$output): (void|false) $callback + * + * @return $this + */ + public function hook(string $name, string $type, callable $callback): self + { + if (!in_array($type, self::FILTER_TYPES, true)) { + $noticeMessage = "Invalid filter type '$type', use " . join('|', self::FILTER_TYPES); + + trigger_error($noticeMessage, E_USER_NOTICE); + } + + $this->filters[$name][$type][] = $callback; + + return $this; + } + + /** + * Executes a chain of method filters. + * + * @param array &$params, mixed &$output): (void|false)> $filters + * Chain of filters- + * @param array $params Method parameters + * @param mixed $output Method output + * + * @throws Exception If an event throws an `Exception` or if `$filters` contains an invalid filter. + */ + public static function filter(array $filters, array &$params, &$output): void + { + foreach ($filters as $key => $callback) { + if (!is_callable($callback)) { + throw new InvalidArgumentException("Invalid callable \$filters[$key]."); + } + + $continue = $callback($params, $output); + + if ($continue === false) { + break; + } + } + } + + /** + * Executes a callback function. + * + * @param callable-string|(Closure(): mixed)|array{class-string|object, string} $callback + * Callback function + * @param array $params Function parameters + * + * @return mixed Function results + * @throws Exception If `$callback` also throws an `Exception`. + */ + public static function execute($callback, array &$params = []) + { + $isInvalidFunctionName = ( + is_string($callback) + && !function_exists($callback) + ); + + if ($isInvalidFunctionName) { + throw new InvalidArgumentException('Invalid callback specified.'); + } + + if (is_array($callback)) { + return self::invokeMethod($callback, $params); + } + + return self::callFunction($callback, $params); + } + + /** + * Calls a function. + * + * @param callable $func Name of function to call + * @param array &$params Function parameters + * + * @return mixed Function results + */ + public static function callFunction(callable $func, array &$params = []) + { + return call_user_func_array($func, $params); + } + + /** + * Invokes a method. + * + * @param array{class-string|object, string} $func Class method + * @param array &$params Class method parameters + * + * @return mixed Function results + * @throws TypeError For unexistent class name. + */ + public static function invokeMethod(array $func, array &$params = []) + { + [$class, $method] = $func; + + if (is_string($class) && class_exists($class)) { + $constructor = (new ReflectionClass($class))->getConstructor(); + $constructorParamsNumber = 0; + + if ($constructor !== null) { + $constructorParamsNumber = count($constructor->getParameters()); + } + + if ($constructorParamsNumber > 0) { + $exceptionMessage = "Method '$class::$method' cannot be called statically. "; + $exceptionMessage .= sprintf( + "$class::__construct require $constructorParamsNumber parameter%s", + $constructorParamsNumber > 1 ? 's' : '' + ); + + throw new InvalidArgumentException($exceptionMessage, E_ERROR); + } + + $class = new $class(); + } + + return call_user_func_array([$class, $method], $params); + } + + /** + * Resets the object to the initial state. + * + * @return $this + */ + public function reset(): self + { + $this->events = []; + $this->filters = []; + + return $this; + } +} diff --git a/api-rest/vendor/flightphp/core/flight/core/Loader.php b/api-rest/vendor/flightphp/core/flight/core/Loader.php new file mode 100644 index 0000000..9792949 --- /dev/null +++ b/api-rest/vendor/flightphp/core/flight/core/Loader.php @@ -0,0 +1,223 @@ + + */ +class Loader +{ + /** + * Registered classes. + * + * @var array, ?callable}> $classes + */ + protected array $classes = []; + + /** + * Class instances. + * + * @var array + */ + protected array $instances = []; + + /** + * Autoload directories. + * + * @var array + */ + protected static array $dirs = []; + + /** + * Registers a class. + * + * @param string $name Registry name + * @param class-string|Closure(): T $class Class name or function to instantiate class + * @param array $params Class initialization parameters + * @param ?Closure(T $instance): void $callback $callback Function to call after object instantiation + * + * @template T of object + */ + public function register(string $name, $class, array $params = [], ?callable $callback = null): void + { + unset($this->instances[$name]); + + $this->classes[$name] = [$class, $params, $callback]; + } + + /** + * Unregisters a class. + * + * @param string $name Registry name + */ + public function unregister(string $name): void + { + unset($this->classes[$name]); + } + + /** + * Loads a registered class. + * + * @param string $name Method name + * @param bool $shared Shared instance + * + * @throws Exception + * + * @return ?object Class instance + */ + public function load(string $name, bool $shared = true): ?object + { + $obj = null; + + if (isset($this->classes[$name])) { + [0 => $class, 1 => $params, 2 => $callback] = $this->classes[$name]; + + $exists = isset($this->instances[$name]); + + if ($shared) { + $obj = ($exists) ? + $this->getInstance($name) : + $this->newInstance($class, $params); + + if (!$exists) { + $this->instances[$name] = $obj; + } + } else { + $obj = $this->newInstance($class, $params); + } + + if ($callback && (!$shared || !$exists)) { + $ref = [&$obj]; + \call_user_func_array($callback, $ref); + } + } + + return $obj; + } + + /** + * Gets a single instance of a class. + * + * @param string $name Instance name + * + * @return ?object Class instance + */ + public function getInstance(string $name): ?object + { + return $this->instances[$name] ?? null; + } + + /** + * Gets a new instance of a class. + * + * @param class-string|Closure(): class-string $class Class name or callback function to instantiate class + * @param array $params Class initialization parameters + * + * @template T of object + * + * @throws Exception + * + * @return T Class instance + */ + public function newInstance($class, array $params = []) + { + if (\is_callable($class)) { + return \call_user_func_array($class, $params); + } + + return new $class(...$params); + } + + /** + * Gets a registered callable + * + * @param string $name Registry name + * + * @return mixed Class information or null if not registered + */ + public function get(string $name) + { + return $this->classes[$name] ?? null; + } + + /** + * Resets the object to the initial state. + */ + public function reset(): void + { + $this->classes = []; + $this->instances = []; + } + + // Autoloading Functions + + /** + * Starts/stops autoloader. + * + * @param bool $enabled Enable/disable autoloading + * @param string|iterable $dirs Autoload directories + */ + public static function autoload(bool $enabled = true, $dirs = []): void + { + if ($enabled) { + spl_autoload_register([__CLASS__, 'loadClass']); + } else { + spl_autoload_unregister([__CLASS__, 'loadClass']); // @codeCoverageIgnore + } + + if (!empty($dirs)) { + self::addDirectory($dirs); + } + } + + /** + * Autoloads classes. + * + * Classes are not allowed to have underscores in their names. + * + * @param string $class Class name + */ + public static function loadClass(string $class): void + { + $classFile = str_replace(['\\', '_'], '/', $class) . '.php'; + + foreach (self::$dirs as $dir) { + $filePath = "$dir/$classFile"; + + if (file_exists($filePath)) { + require_once $filePath; + + return; + } + } + } + + /** + * Adds a directory for autoloading classes. + * + * @param string|iterable $dir Directory path + */ + public static function addDirectory($dir): void + { + if (\is_array($dir) || \is_object($dir)) { + foreach ($dir as $value) { + self::addDirectory($value); + } + } elseif (\is_string($dir)) { + if (!\in_array($dir, self::$dirs, true)) { + self::$dirs[] = $dir; + } + } + } +} diff --git a/api-rest/vendor/flightphp/core/flight/database/PdoWrapper.php b/api-rest/vendor/flightphp/core/flight/database/PdoWrapper.php new file mode 100644 index 0000000..bdbabb8 --- /dev/null +++ b/api-rest/vendor/flightphp/core/flight/database/PdoWrapper.php @@ -0,0 +1,149 @@ +runQuery("SELECT * FROM table WHERE something = ?", [ $something ]); + * while($row = $statement->fetch()) { + * // ... + * } + * + * $db->runQuery("INSERT INTO table (name) VALUES (?)", [ $name ]); + * $db->runQuery("UPDATE table SET name = ? WHERE id = ?", [ $name, $id ]); + * + * @param string $sql - Ex: "SELECT * FROM table WHERE something = ?" + * @param array $params - Ex: [ $something ] + * + * @return PDOStatement + */ + public function runQuery(string $sql, array $params = []): PDOStatement + { + $processed_sql_data = $this->processInStatementSql($sql, $params); + $sql = $processed_sql_data['sql']; + $params = $processed_sql_data['params']; + $statement = $this->prepare($sql); + $statement->execute($params); + return $statement; + } + + /** + * Pulls one field from the query + * + * Ex: $id = $db->fetchField("SELECT id FROM table WHERE something = ?", [ $something ]); + * + * @param string $sql - Ex: "SELECT id FROM table WHERE something = ?" + * @param array $params - Ex: [ $something ] + * + * @return mixed + */ + public function fetchField(string $sql, array $params = []) + { + $result = $this->fetchRow($sql, $params); + return reset($result); + } + + /** + * Pulls one row from the query + * + * Ex: $row = $db->fetchRow("SELECT * FROM table WHERE something = ?", [ $something ]); + * + * @param string $sql - Ex: "SELECT * FROM table WHERE something = ?" + * @param array $params - Ex: [ $something ] + * + * @return Collection + */ + public function fetchRow(string $sql, array $params = []): Collection + { + $sql .= stripos($sql, 'LIMIT') === false ? ' LIMIT 1' : ''; + $result = $this->fetchAll($sql, $params); + return count($result) > 0 ? $result[0] : new Collection(); + } + + /** + * Pulls all rows from the query + * + * Ex: $rows = $db->fetchAll("SELECT * FROM table WHERE something = ?", [ $something ]); + * foreach($rows as $row) { + * // ... + * } + * + * @param string $sql - Ex: "SELECT * FROM table WHERE something = ?" + * @param array $params - Ex: [ $something ] + * + * @return array + */ + public function fetchAll(string $sql, array $params = []) + { + $processed_sql_data = $this->processInStatementSql($sql, $params); + $sql = $processed_sql_data['sql']; + $params = $processed_sql_data['params']; + $statement = $this->prepare($sql); + $statement->execute($params); + $results = $statement->fetchAll(); + if (is_array($results) === true && count($results) > 0) { + foreach ($results as &$result) { + $result = new Collection($result); + } + } else { + $results = []; + } + return $results; + } + + /** + * Don't worry about this guy. Converts stuff for IN statements + * + * Ex: $row = $db->fetchAll("SELECT * FROM table WHERE id = ? AND something IN(?), [ $id, [1,2,3] ]); + * Converts this to "SELECT * FROM table WHERE id = ? AND something IN(?,?,?)" + * + * @param string $sql the sql statement + * @param array $params the params for the sql statement + * + * @return array> + */ + protected function processInStatementSql(string $sql, array $params = []): array + { + // Replace "IN(?)" with "IN(?,?,?)" + $sql = preg_replace('/IN\s*\(\s*\?\s*\)/i', 'IN(?)', $sql); + + $current_index = 0; + while (($current_index = strpos($sql, 'IN(?)', $current_index)) !== false) { + $preceeding_count = substr_count($sql, '?', 0, $current_index - 1); + + $param = $params[$preceeding_count]; + $question_marks = '?'; + + if (is_string($param) || is_array($param)) { + $params_to_use = $param; + if (is_string($param)) { + $params_to_use = explode(',', $param); + } + + foreach ($params_to_use as $key => $value) { + if (is_string($value)) { + $params_to_use[$key] = trim($value); + } + } + + $question_marks = join(',', array_fill(0, count($params_to_use), '?')); + $sql = substr_replace($sql, $question_marks, $current_index + 3, 1); + + array_splice($params, $preceeding_count, 1, $params_to_use); + } + + $current_index += strlen($question_marks) + 4; + } + + return ['sql' => $sql, 'params' => $params]; + } +} diff --git a/api-rest/vendor/flightphp/core/flight/net/Request.php b/api-rest/vendor/flightphp/core/flight/net/Request.php new file mode 100644 index 0000000..569994e --- /dev/null +++ b/api-rest/vendor/flightphp/core/flight/net/Request.php @@ -0,0 +1,417 @@ + + * + * The default request properties are: + * + * - **url** - The URL being requested + * - **base** - The parent subdirectory of the URL + * - **method** - The request method (GET, POST, PUT, DELETE) + * - **referrer** - The referrer URL + * - **ip** - IP address of the client + * - **ajax** - Whether the request is an AJAX request + * - **scheme** - The server protocol (http, https) + * - **user_agent** - Browser information + * - **type** - The content type + * - **length** - The content length + * - **query** - Query string parameters + * - **data** - Post parameters + * - **cookies** - Cookie parameters + * - **files** - Uploaded files + * - **secure** - Connection is secure + * - **accept** - HTTP accept parameters + * - **proxy_ip** - Proxy IP address of the client + */ +class Request +{ + /** + * URL being requested + */ + public string $url; + + /** + * Parent subdirectory of the URL + */ + public string $base; + + /** + * Request method (GET, POST, PUT, DELETE) + */ + public string $method; + + /** + * Referrer URL + */ + public string $referrer; + + /** + * IP address of the client + */ + public string $ip; + + /** + * Whether the request is an AJAX request + */ + public bool $ajax; + + /** + * Server protocol (http, https) + */ + public string $scheme; + + /** + * Browser information + */ + public string $user_agent; + + /** + * Content type + */ + public string $type; + + /** + * Content length + */ + public int $length; + + /** + * Query string parameters + */ + public Collection $query; + + /** + * Post parameters + */ + public Collection $data; + + /** + * Cookie parameters + */ + public Collection $cookies; + + /** + * Uploaded files + */ + public Collection $files; + + /** + * Whether the connection is secure + */ + public bool $secure; + + /** + * HTTP accept parameters + */ + public string $accept; + + /** + * Proxy IP address of the client + */ + public string $proxy_ip; + + /** + * HTTP host name + */ + public string $host; + + /** + * Stream path for where to pull the request body from + */ + private string $stream_path = 'php://input'; + + /** + * Raw HTTP request body + */ + public string $body = ''; + + /** + * Constructor. + * + * @param array $config Request configuration + */ + public function __construct(array $config = []) + { + // Default properties + if (empty($config)) { + $config = [ + 'url' => str_replace('@', '%40', self::getVar('REQUEST_URI', '/')), + 'base' => str_replace(['\\', ' '], ['/', '%20'], \dirname(self::getVar('SCRIPT_NAME'))), + 'method' => self::getMethod(), + 'referrer' => self::getVar('HTTP_REFERER'), + 'ip' => self::getVar('REMOTE_ADDR'), + 'ajax' => 'XMLHttpRequest' === self::getVar('HTTP_X_REQUESTED_WITH'), + 'scheme' => self::getScheme(), + 'user_agent' => self::getVar('HTTP_USER_AGENT'), + 'type' => self::getVar('CONTENT_TYPE'), + 'length' => intval(self::getVar('CONTENT_LENGTH', 0)), + 'query' => new Collection($_GET), + 'data' => new Collection($_POST), + 'cookies' => new Collection($_COOKIE), + 'files' => new Collection($_FILES), + 'secure' => 'https' === self::getScheme(), + 'accept' => self::getVar('HTTP_ACCEPT'), + 'proxy_ip' => self::getProxyIpAddress(), + 'host' => self::getVar('HTTP_HOST'), + ]; + } + + $this->init($config); + } + + /** + * Initialize request properties. + * + * @param array $properties Array of request properties + * + * @return self + */ + public function init(array $properties = []): self + { + // Set all the defined properties + foreach ($properties as $name => $value) { + $this->{$name} = $value; + } + + // Get the requested URL without the base directory + // This rewrites the url in case the public url and base directories match + // (such as installing on a subdirectory in a web server) + // @see testInitUrlSameAsBaseDirectory + if ('/' !== $this->base && '' !== $this->base && 0 === strpos($this->url, $this->base)) { + $this->url = substr($this->url, \strlen($this->base)); + } + + // Default url + if (empty($this->url)) { + $this->url = '/'; + } else { + // Merge URL query parameters with $_GET + $_GET = array_merge($_GET, self::parseQuery($this->url)); + + $this->query->setData($_GET); + } + + // Check for JSON input + if (0 === strpos($this->type, 'application/json')) { + $body = $this->getBody(); + if ('' !== $body) { + $data = json_decode($body, true); + if (is_array($data)) { + $this->data->setData($data); + } + } + } + + return $this; + } + + /** + * Gets the body of the request. + * + * @return string Raw HTTP request body + */ + public function getBody(): string + { + $body = $this->body; + + if ('' !== $body) { + return $body; + } + + $method = $this->method ?? self::getMethod(); + + if ('POST' === $method || 'PUT' === $method || 'DELETE' === $method || 'PATCH' === $method) { + $body = file_get_contents($this->stream_path); + } + + $this->body = $body; + + return $body; + } + + /** + * Gets the request method. + */ + public static function getMethod(): string + { + $method = self::getVar('REQUEST_METHOD', 'GET'); + + if (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) { + $method = $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']; + } elseif (isset($_REQUEST['_method'])) { + $method = $_REQUEST['_method']; + } + + return strtoupper($method); + } + + /** + * Gets the real remote IP address. + * + * @return string IP address + */ + public static function getProxyIpAddress(): string + { + $forwarded = [ + 'HTTP_CLIENT_IP', + 'HTTP_X_FORWARDED_FOR', + 'HTTP_X_FORWARDED', + 'HTTP_X_CLUSTER_CLIENT_IP', + 'HTTP_FORWARDED_FOR', + 'HTTP_FORWARDED', + ]; + + $flags = \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE; + + foreach ($forwarded as $key) { + if (\array_key_exists($key, $_SERVER)) { + sscanf($_SERVER[$key], '%[^,]', $ip); + if (false !== filter_var($ip, \FILTER_VALIDATE_IP, $flags)) { + return $ip; + } + } + } + + return ''; + } + + /** + * Gets a variable from $_SERVER using $default if not provided. + * + * @param string $var Variable name + * @param mixed $default Default value to substitute + * + * @return mixed Server variable value + */ + public static function getVar(string $var, $default = '') + { + return $_SERVER[$var] ?? $default; + } + + /** + * This will pull a header from the request. + * + * @param string $header Header name. Can be caps, lowercase, or mixed. + * @param string $default Default value if the header does not exist + * + * @return string + */ + public static function getHeader(string $header, $default = ''): string + { + $header = 'HTTP_' . strtoupper(str_replace('-', '_', $header)); + return self::getVar($header, $default); + } + + /** + * Gets all the request headers + * + * @return array + */ + public static function getHeaders(): array + { + $headers = []; + foreach ($_SERVER as $key => $value) { + if (0 === strpos($key, 'HTTP_')) { + // converts headers like HTTP_CUSTOM_HEADER to Custom-Header + $key = str_replace(' ', '-', ucwords(str_replace('_', ' ', strtolower(substr($key, 5))))); + $headers[$key] = $value; + } + } + return $headers; + } + + /** + * Alias of Request->getHeader(). Gets a single header. + * + * @param string $header Header name. Can be caps, lowercase, or mixed. + * @param string $default Default value if the header does not exist + * + * @return string + */ + public static function header(string $header, $default = '') + { + return self::getHeader($header, $default); + } + + /** + * Alias of Request->getHeaders(). Gets all the request headers + * + * @return array + */ + public static function headers(): array + { + return self::getHeaders(); + } + + /** + * Gets the full request URL. + * + * @return string URL + */ + public function getFullUrl(): string + { + return $this->scheme . '://' . $this->host . $this->url; + } + + /** + * Grabs the scheme and host. Does not end with a / + * + * @return string + */ + public function getBaseUrl(): string + { + return $this->scheme . '://' . $this->host; + } + + /** + * Parse query parameters from a URL. + * + * @param string $url URL string + * + * @return array> + */ + public static function parseQuery(string $url): array + { + $params = []; + + $args = parse_url($url); + if (isset($args['query'])) { + parse_str($args['query'], $params); + } + + return $params; + } + + /** + * Gets the URL Scheme + * + * @return string 'http'|'https' + */ + public static function getScheme(): string + { + if ( + (isset($_SERVER['HTTPS']) && 'on' === strtolower($_SERVER['HTTPS'])) + || + (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO']) + || + (isset($_SERVER['HTTP_FRONT_END_HTTPS']) && 'on' === $_SERVER['HTTP_FRONT_END_HTTPS']) + || + (isset($_SERVER['REQUEST_SCHEME']) && 'https' === $_SERVER['REQUEST_SCHEME']) + ) { + return 'https'; + } + + return 'http'; + } +} diff --git a/api-rest/vendor/flightphp/core/flight/net/Response.php b/api-rest/vendor/flightphp/core/flight/net/Response.php new file mode 100644 index 0000000..e1abaac --- /dev/null +++ b/api-rest/vendor/flightphp/core/flight/net/Response.php @@ -0,0 +1,433 @@ + + */ +class Response +{ + /** + * Content-Length header. + */ + public bool $content_length = true; + + /** + * This is to maintain legacy handling of output buffering + * which causes a lot of problems. This will be removed + * in v4 + * + * @var boolean + */ + public bool $v2_output_buffering = false; + + /** + * HTTP status codes + * + * @var array $codes + */ + public static array $codes = [ + 100 => 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', + 208 => 'Already Reported', + + 226 => 'IM Used', + + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => '(Unused)', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', + + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Payload Too Large', + 414 => 'URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Range Not Satisfiable', + 417 => 'Expectation Failed', + + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + + 426 => 'Upgrade Required', + + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + + 431 => 'Request Header Fields Too Large', + + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 506 => 'Variant Also Negotiates', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + + 510 => 'Not Extended', + 511 => 'Network Authentication Required', + ]; + + /** + * HTTP status + */ + protected int $status = 200; + + /** + * HTTP response headers + * + * @var array> $headers + */ + protected array $headers = []; + + /** + * HTTP response body + */ + protected string $body = ''; + + /** + * HTTP response sent + */ + protected bool $sent = false; + + /** + * Sets the HTTP status of the response. + * + * @param ?int $code HTTP status code. + * + * @throws Exception If invalid status code + * + * @return int|$this Self reference + */ + public function status(?int $code = null) + { + if (null === $code) { + return $this->status; + } + + if (\array_key_exists($code, self::$codes)) { + $this->status = $code; + } else { + throw new Exception('Invalid status code.'); + } + + return $this; + } + + /** + * Adds a header to the response. + * + * @param array|string $name Header name or array of names and values + * @param ?string $value Header value + * + * @return $this + */ + public function header($name, ?string $value = null): self + { + if (\is_array($name)) { + foreach ($name as $k => $v) { + $this->headers[$k] = $v; + } + } else { + $this->headers[$name] = $value; + } + + return $this; + } + + /** + * Gets a single header from the response. + * + * @param string $name the name of the header + * + * @return string|null + */ + public function getHeader(string $name): ?string + { + $headers = $this->headers; + // lowercase all the header keys + $headers = array_change_key_case($headers, CASE_LOWER); + return $headers[strtolower($name)] ?? null; + } + + /** + * Alias of Response->header(). Adds a header to the response. + * + * @param array|string $name Header name or array of names and values + * @param ?string $value Header value + * + * @return $this + */ + public function setHeader($name, ?string $value): self + { + return $this->header($name, $value); + } + + /** + * Returns the headers from the response. + * + * @return array> + */ + public function headers(): array + { + return $this->headers; + } + + /** + * Alias for Response->headers(). Returns the headers from the response. + * + * @return array> + */ + public function getHeaders(): array + { + return $this->headers(); + } + + /** + * Writes content to the response body. + * + * @param string $str Response content + * @param bool $overwrite Overwrite the response body + * + * @return $this Self reference + */ + public function write(string $str, bool $overwrite = false): self + { + if ($overwrite === true) { + $this->clearBody(); + } + + $this->body .= $str; + + return $this; + } + + /** + * Clears the response body. + * + * @return $this Self reference + */ + public function clearBody(): self + { + $this->body = ''; + return $this; + } + + /** + * Clears the response. + * + * @return $this Self reference + */ + public function clear(): self + { + $this->status = 200; + $this->headers = []; + $this->clearBody(); + + // This needs to clear the output buffer if it's on + if ($this->v2_output_buffering === false && ob_get_length() > 0) { + ob_clean(); + } + + return $this; + } + + /** + * Sets caching headers for the response. + * + * @param int|string|false $expires Expiration time as time() or as strtotime() string value + * + * @return $this Self reference + */ + public function cache($expires): self + { + if (false === $expires) { + $this->headers['Expires'] = 'Mon, 26 Jul 1997 05:00:00 GMT'; + $this->headers['Cache-Control'] = [ + 'no-store, no-cache, must-revalidate', + 'post-check=0, pre-check=0', + 'max-age=0', + ]; + $this->headers['Pragma'] = 'no-cache'; + } else { + $expires = \is_int($expires) ? $expires : strtotime($expires); + $this->headers['Expires'] = gmdate('D, d M Y H:i:s', $expires) . ' GMT'; + $this->headers['Cache-Control'] = 'max-age=' . ($expires - time()); + if (isset($this->headers['Pragma']) && 'no-cache' == $this->headers['Pragma']) { + unset($this->headers['Pragma']); + } + } + + return $this; + } + + /** + * Sends HTTP headers. + * + * @return $this Self reference + */ + public function sendHeaders(): self + { + // Send status code header + if (false !== strpos(\PHP_SAPI, 'cgi')) { + // @codeCoverageIgnoreStart + $this->setRealHeader( + sprintf( + 'Status: %d %s', + $this->status, + self::$codes[$this->status] + ), + true + ); + // @codeCoverageIgnoreEnd + } else { + $this->setRealHeader( + sprintf( + '%s %d %s', + $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1', + $this->status, + self::$codes[$this->status] + ), + true, + $this->status + ); + } + + // Send other headers + foreach ($this->headers as $field => $value) { + if (\is_array($value)) { + foreach ($value as $v) { + $this->setRealHeader($field . ': ' . $v, false); + } + } else { + $this->setRealHeader($field . ': ' . $value); + } + } + + if ($this->content_length) { + // Send content length + $length = $this->getContentLength(); + + if ($length > 0) { + $this->setRealHeader('Content-Length: ' . $length); + } + } + + return $this; + } + + /** + * Sets a real header. Mostly used for test mocking. + * + * @param string $header_string The header string you would pass to header() + * @param bool $replace The optional replace parameter indicates whether the + * header should replace a previous similar header, or add a second header of + * the same type. By default it will replace, but if you pass in false as the + * second argument you can force multiple headers of the same type. + * @param int $response_code The response code to send + * + * @return self + * + * @codeCoverageIgnore + */ + public function setRealHeader(string $header_string, bool $replace = true, int $response_code = 0): self + { + header($header_string, $replace, $response_code); + return $this; + } + + /** + * Gets the content length. + */ + public function getContentLength(): int + { + return \extension_loaded('mbstring') ? + mb_strlen($this->body, 'latin1') : + \strlen($this->body); + } + + /** + * Gets the response body + * + * @return string + */ + public function getBody(): string + { + return $this->body; + } + + /** + * Gets whether response body was sent. + */ + public function sent(): bool + { + return $this->sent; + } + + /** + * Marks the response as sent. + */ + public function markAsSent(): void + { + $this->sent = true; + } + + /** + * Sends a HTTP response. + */ + public function send(): void + { + // legacy way of handling this + if ($this->v2_output_buffering === true) { + if (ob_get_length() > 0) { + ob_end_clean(); // @codeCoverageIgnore + } + } + + if (!headers_sent()) { + $this->sendHeaders(); // @codeCoverageIgnore + } + + echo $this->body; + + $this->sent = true; + } +} diff --git a/api-rest/vendor/flightphp/core/flight/net/Route.php b/api-rest/vendor/flightphp/core/flight/net/Route.php new file mode 100644 index 0000000..4abf2ae --- /dev/null +++ b/api-rest/vendor/flightphp/core/flight/net/Route.php @@ -0,0 +1,258 @@ + + */ +class Route +{ + /** + * URL pattern + */ + public string $pattern; + + /** + * Callback function + * + * @var mixed + */ + public $callback; + + /** + * HTTP methods + * + * @var array + */ + public array $methods = []; + + /** + * Route parameters + * + * @var array + */ + public array $params = []; + + /** + * Matching regular expression + */ + public ?string $regex = null; + + /** + * URL splat content + */ + public string $splat = ''; + + /** + * Pass self in callback parameters + */ + public bool $pass = false; + + /** + * The alias is a way to identify the route using a simple name ex: 'login' instead of /admin/login + */ + public string $alias = ''; + + /** + * The middleware to be applied to the route + * + * @var array + */ + public array $middleware = []; + + /** Whether the response for this route should be streamed. */ + public bool $is_streamed = false; + + /** + * If this route is streamed, the headers to be sent before the response. + * + * @var array + */ + public array $streamed_headers = []; + + /** + * Constructor. + * + * @param string $pattern URL pattern + * @param callable $callback Callback function + * @param array $methods HTTP methods + * @param bool $pass Pass self in callback parameters + */ + public function __construct(string $pattern, callable $callback, array $methods, bool $pass, string $alias = '') + { + $this->pattern = $pattern; + $this->callback = $callback; + $this->methods = $methods; + $this->pass = $pass; + $this->alias = $alias; + } + + /** + * Checks if a URL matches the route pattern. Also parses named parameters in the URL. + * + * @param string $url Requested URL (original format, not URL decoded) + * @param bool $case_sensitive Case sensitive matching + * + * @return bool Match status + */ + public function matchUrl(string $url, bool $case_sensitive = false): bool + { + // Wildcard or exact match + if ('*' === $this->pattern || $this->pattern === $url) { + return true; + } + + $ids = []; + $last_char = substr($this->pattern, -1); + + // Get splat + if ($last_char === '*') { + $n = 0; + $len = \strlen($url); + $count = substr_count($this->pattern, '/'); + + for ($i = 0; $i < $len; $i++) { + if ($url[$i] === '/') { + $n++; + } + if ($n === $count) { + break; + } + } + + $this->splat = urldecode(strval(substr($url, $i + 1))); + } + + // Build the regex for matching + $pattern_utf_chars_encoded = preg_replace_callback( + '#(\\p{L}+)#u', + static function ($matches) { + return urlencode($matches[0]); + }, + $this->pattern + ); + $regex = str_replace([')', '/*'], [')?', '(/?|/.*?)'], $pattern_utf_chars_encoded); + + $regex = preg_replace_callback( + '#@([\w]+)(:([^/\(\)]*))?#', + static function ($matches) use (&$ids) { + $ids[$matches[1]] = null; + if (isset($matches[3])) { + return '(?P<' . $matches[1] . '>' . $matches[3] . ')'; + } + + return '(?P<' . $matches[1] . '>[^/\?]+)'; + }, + $regex + ); + + if ('/' === $last_char) { // Fix trailing slash + $regex .= '?'; + } else { // Allow trailing slash + $regex .= '/?'; + } + + // Attempt to match route and named parameters + if (preg_match('#^' . $regex . '(?:\?[\s\S]*)?$#' . (($case_sensitive) ? '' : 'i'), $url, $matches)) { + foreach ($ids as $k => $v) { + $this->params[$k] = (\array_key_exists($k, $matches)) ? urldecode($matches[$k]) : null; + } + + $this->regex = $regex; + + return true; + } + + return false; + } + + /** + * Checks if an HTTP method matches the route methods. + * + * @param string $method HTTP method + * + * @return bool Match status + */ + public function matchMethod(string $method): bool + { + return \count(array_intersect([$method, '*'], $this->methods)) > 0; + } + + /** + * Checks if an alias matches the route alias. + */ + public function matchAlias(string $alias): bool + { + return $this->alias === $alias; + } + + /** + * Hydrates the route url with the given parameters + * + * @param array $params the parameters to pass to the route + */ + public function hydrateUrl(array $params = []): string + { + $url = preg_replace_callback("/(?:@([\w]+)(?:\:([^\/]+))?\)*)/i", function ($match) use ($params) { + if (isset($match[1]) && isset($params[$match[1]])) { + return $params[$match[1]]; + } + }, $this->pattern); + + // catches potential optional parameter + $url = str_replace('(/', '/', $url); + // trim any trailing slashes + if ($url !== '/') { + $url = rtrim($url, '/'); + } + return $url; + } + + /** + * Sets the route alias + * + * @return $this + */ + public function setAlias(string $alias): self + { + $this->alias = $alias; + return $this; + } + + /** + * Sets the route middleware + * + * @param array|callable $middleware + */ + public function addMiddleware($middleware): self + { + if (is_array($middleware) === true) { + $this->middleware = array_merge($this->middleware, $middleware); + } else { + $this->middleware[] = $middleware; + } + return $this; + } + + /** + * This will allow the response for this route to be streamed. + * + * @param array $headers a key value of headers to set before the stream starts. + * + * @return $this + */ + public function streamWithHeaders(array $headers): self + { + $this->is_streamed = true; + $this->streamed_headers = $headers; + + return $this; + } +} diff --git a/api-rest/vendor/flightphp/core/flight/net/Router.php b/api-rest/vendor/flightphp/core/flight/net/Router.php new file mode 100644 index 0000000..81556cd --- /dev/null +++ b/api-rest/vendor/flightphp/core/flight/net/Router.php @@ -0,0 +1,317 @@ + + */ +class Router +{ + /** + * Case sensitive matching. + */ + public bool $case_sensitive = false; + + /** + * Mapped routes. + * + * @var array $routes + */ + protected array $routes = []; + + /** + * The current route that is has been found and executed. + */ + protected ?Route $executedRoute = null; + + /** + * Pointer to current route. + */ + protected int $index = 0; + + /** + * When groups are used, this is mapped against all the routes + */ + protected string $group_prefix = ''; + + /** + * Group Middleware + * + * @var array + */ + protected array $group_middlewares = []; + + /** + * Allowed HTTP methods + * + * @var array + */ + protected array $allowed_methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']; + + /** + * Gets mapped routes. + * + * @return array Array of routes + */ + public function getRoutes(): array + { + return $this->routes; + } + + /** + * Clears all routes in the router. + */ + public function clear(): void + { + $this->routes = []; + } + + /** + * Maps a URL pattern to a callback function. + * + * @param string $pattern URL pattern to match. + * @param callable $callback Callback function. + * @param bool $pass_route Pass the matching route object to the callback. + * @param string $route_alias Alias for the route. + */ + public function map(string $pattern, callable $callback, bool $pass_route = false, string $route_alias = ''): Route + { + + // This means that the route ies defined in a group, but the defined route is the base + // url path. Note the '' in route() + // Ex: Flight::group('/api', function() { + // Flight::route('', function() {}); + // } + // Keep the space so that it can execute the below code normally + if ($this->group_prefix !== '') { + $url = ltrim($pattern); + } else { + $url = trim($pattern); + } + + $methods = ['*']; + + if (false !== strpos($url, ' ')) { + [$method, $url] = explode(' ', $url, 2); + $url = trim($url); + $methods = explode('|', $method); + + // Add head requests to get methods, should they come in as a get request + if (in_array('GET', $methods, true) === true && in_array('HEAD', $methods, true) === false) { + $methods[] = 'HEAD'; + } + } + + // And this finishes it off. + if ($this->group_prefix !== '') { + $url = rtrim($this->group_prefix . $url); + } + + $route = new Route($url, $callback, $methods, $pass_route, $route_alias); + + // to handle group middleware + foreach ($this->group_middlewares as $gm) { + $route->addMiddleware($gm); + } + + $this->routes[] = $route; + + return $route; + } + + /** + * Creates a GET based route + * + * @param string $pattern URL pattern to match + * @param callable $callback Callback function + * @param bool $pass_route Pass the matching route object to the callback + * @param string $alias Alias for the route + */ + public function get(string $pattern, callable $callback, bool $pass_route = false, string $alias = ''): Route + { + return $this->map('GET ' . $pattern, $callback, $pass_route, $alias); + } + + /** + * Creates a POST based route + * + * @param string $pattern URL pattern to match + * @param callable $callback Callback function + * @param bool $pass_route Pass the matching route object to the callback + * @param string $alias Alias for the route + */ + public function post(string $pattern, callable $callback, bool $pass_route = false, string $alias = ''): Route + { + return $this->map('POST ' . $pattern, $callback, $pass_route, $alias); + } + + /** + * Creates a PUT based route + * + * @param string $pattern URL pattern to match + * @param callable $callback Callback function + * @param bool $pass_route Pass the matching route object to the callback + * @param string $alias Alias for the route + */ + public function put(string $pattern, callable $callback, bool $pass_route = false, string $alias = ''): Route + { + return $this->map('PUT ' . $pattern, $callback, $pass_route, $alias); + } + + /** + * Creates a PATCH based route + * + * @param string $pattern URL pattern to match + * @param callable $callback Callback function + * @param bool $pass_route Pass the matching route object to the callback + * @param string $alias Alias for the route + */ + public function patch(string $pattern, callable $callback, bool $pass_route = false, string $alias = ''): Route + { + return $this->map('PATCH ' . $pattern, $callback, $pass_route, $alias); + } + + /** + * Creates a DELETE based route + * + * @param string $pattern URL pattern to match + * @param callable $callback Callback function + * @param bool $pass_route Pass the matching route object to the callback + * @param string $alias Alias for the route + */ + public function delete(string $pattern, callable $callback, bool $pass_route = false, string $alias = ''): Route + { + return $this->map('DELETE ' . $pattern, $callback, $pass_route, $alias); + } + + /** + * Group together a set of routes + * + * @param string $group_prefix group URL prefix (such as /api/v1) + * @param callable $callback The necessary calling that holds the Router class + * @param array $group_middlewares + * The middlewares to be applied to the group. Example: `[$middleware1, $middleware2]` + */ + public function group(string $group_prefix, callable $callback, array $group_middlewares = []): void + { + $old_group_prefix = $this->group_prefix; + $old_group_middlewares = $this->group_middlewares; + $this->group_prefix .= $group_prefix; + $this->group_middlewares = array_merge($this->group_middlewares, $group_middlewares); + $callback($this); + $this->group_prefix = $old_group_prefix; + $this->group_middlewares = $old_group_middlewares; + } + + /** + * Routes the current request. + * + * @return false|Route Matching route or false if no match + */ + public function route(Request $request) + { + while ($route = $this->current()) { + if ($route->matchMethod($request->method) && $route->matchUrl($request->url, $this->case_sensitive)) { + $this->executedRoute = $route; + return $route; + } + $this->next(); + } + + return false; + } + + /** + * Gets the URL for a given route alias + * + * @param string $alias the alias to match + * @param array $params the parameters to pass to the route + */ + public function getUrlByAlias(string $alias, array $params = []): string + { + $potential_aliases = []; + foreach ($this->routes as $route) { + $potential_aliases[] = $route->alias; + if ($route->matchAlias($alias)) { + // This will make it so the params that already + // exist in the url will be passed in. + if (!empty($this->executedRoute->params)) { + $params = $params + $this->executedRoute->params; + } + return $route->hydrateUrl($params); + } + } + + // use a levenshtein to find the closest match and make a recommendation + $closest_match = ''; + $closest_match_distance = 0; + foreach ($potential_aliases as $potential_alias) { + $levenshtein_distance = levenshtein($alias, $potential_alias); + if ($levenshtein_distance > $closest_match_distance) { + $closest_match = $potential_alias; + $closest_match_distance = $levenshtein_distance; + } + } + + $exception_message = 'No route found with alias: \'' . $alias . '\'.'; + if ($closest_match !== '') { + $exception_message .= ' Did you mean \'' . $closest_match . '\'?'; + } + + throw new Exception($exception_message); + } + + /** + * Rewinds the current route index. + */ + public function rewind(): void + { + $this->index = 0; + } + + /** + * Checks if more routes can be iterated. + * + * @return bool More routes + */ + public function valid(): bool + { + return isset($this->routes[$this->index]); + } + + /** + * Gets the current route. + * + * @return false|Route + */ + public function current() + { + return $this->routes[$this->index] ?? false; + } + + /** + * Gets the next route. + */ + public function next(): void + { + $this->index++; + } + + /** + * Reset to the first route. + */ + public function reset(): void + { + $this->index = 0; + } +} diff --git a/api-rest/vendor/flightphp/core/flight/template/View.php b/api-rest/vendor/flightphp/core/flight/template/View.php new file mode 100644 index 0000000..d1bc07f --- /dev/null +++ b/api-rest/vendor/flightphp/core/flight/template/View.php @@ -0,0 +1,197 @@ + + */ +class View +{ + /** Location of view templates. */ + public string $path; + + /** File extension. */ + public string $extension = '.php'; + + /** + * View variables. + * + * @var array $vars + */ + protected array $vars = []; + + /** Template file. */ + private string $template; + + /** + * Constructor. + * + * @param string $path Path to templates directory + */ + public function __construct(string $path = '.') + { + $this->path = $path; + } + + /** + * Gets a template variable. + * + * @return mixed Variable value or `null` if doesn't exists + */ + public function get(string $key) + { + return $this->vars[$key] ?? null; + } + + /** + * Sets a template variable. + * + * @param string|iterable $key + * @param mixed $value Value + * + * @return self + */ + public function set($key, $value = null): self + { + if (\is_iterable($key)) { + foreach ($key as $k => $v) { + $this->vars[$k] = $v; + } + } else { + $this->vars[$key] = $value; + } + + return $this; + } + + /** + * Checks if a template variable is set. + * + * @return bool If key exists + */ + public function has(string $key): bool + { + return isset($this->vars[$key]); + } + + /** + * Unsets a template variable. If no key is passed in, clear all variables. + * + * @return $this + */ + public function clear(?string $key = null): self + { + if (null === $key) { + $this->vars = []; + } else { + unset($this->vars[$key]); + } + + return $this; + } + + /** + * Renders a template. + * + * @param string $file Template file + * @param ?array $data Template data + * + * @throws \Exception If template not found + */ + public function render(string $file, ?array $data = null): void + { + $this->template = $this->getTemplate($file); + + if (!\file_exists($this->template)) { + $normalized_path = self::normalizePath($this->template); + throw new \Exception("Template file not found: {$normalized_path}."); + } + + if (\is_array($data)) { + $this->vars = \array_merge($this->vars, $data); + } + + \extract($this->vars); + + include $this->template; + } + + /** + * Gets the output of a template. + * + * @param string $file Template file + * @param ?array $data Template data + * + * @return string Output of template + */ + public function fetch(string $file, ?array $data = null): string + { + \ob_start(); + + $this->render($file, $data); + + return \ob_get_clean(); + } + + /** + * Checks if a template file exists. + * + * @param string $file Template file + * + * @return bool Template file exists + */ + public function exists(string $file): bool + { + return \file_exists($this->getTemplate($file)); + } + + /** + * Gets the full path to a template file. + * + * @param string $file Template file + * + * @return string Template file location + */ + public function getTemplate(string $file): string + { + $ext = $this->extension; + + if (!empty($ext) && (\substr($file, -1 * \strlen($ext)) != $ext)) { + $file .= $ext; + } + + $is_windows = \strtoupper(\substr(PHP_OS, 0, 3)) === 'WIN'; + + if (('/' == \substr($file, 0, 1)) || ($is_windows === true && ':' == \substr($file, 1, 1))) { + return $file; + } + + return $this->path . DIRECTORY_SEPARATOR . $file; + } + + /** + * Displays escaped output. + * + * @param string $str String to escape + * + * @return string Escaped string + */ + public function e(string $str): string + { + $value = \htmlentities($str); + echo $value; + return $value; + } + + protected static function normalizePath(string $path, string $separator = DIRECTORY_SEPARATOR): string + { + return \str_replace(['\\', '/'], $separator, $path); + } +} diff --git a/api-rest/vendor/flightphp/core/flight/util/Collection.php b/api-rest/vendor/flightphp/core/flight/util/Collection.php new file mode 100644 index 0000000..29147d1 --- /dev/null +++ b/api-rest/vendor/flightphp/core/flight/util/Collection.php @@ -0,0 +1,253 @@ + + * @implements ArrayAccess + * @implements Iterator + */ +class Collection implements ArrayAccess, Iterator, Countable, JsonSerializable +{ + /** + * This is to allow for reset() to work properly. + * + * WARNING! This MUST be the first variable in this class!!! + * + * PHPStan is ignoring this because we don't need actually to read the property + * + * @var mixed + * @phpstan-ignore-next-line + */ + private $first_property = null; + + /** + * Collection data. + * + * @var array + */ + private array $data; + + /** + * Constructor. + * + * @param array $data Initial data + */ + public function __construct(array $data = []) + { + $this->data = $data; + $this->handleReset(); + } + + /** + * Gets an item. + * + * @return mixed Value if `$key` exists in collection data, otherwise returns `NULL` + */ + public function __get(string $key) + { + return $this->data[$key] ?? null; + } + + /** + * Set an item. + * + * @param mixed $value Value + */ + public function __set(string $key, $value): void + { + $this->data[$key] = $value; + $this->handleReset(); + } + + /** + * Checks if an item exists. + */ + public function __isset(string $key): bool + { + return isset($this->data[$key]); + } + + /** + * Removes an item. + */ + public function __unset(string $key): void + { + unset($this->data[$key]); + $this->handleReset(); + } + + /** + * Gets an item at the offset. + * + * @param string $offset Offset + * + * @return mixed Value + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + return $this->data[$offset] ?? null; + } + + /** + * Sets an item at the offset. + * + * @param ?string $offset Offset + * @param mixed $value Value + */ + #[\ReturnTypeWillChange] + public function offsetSet($offset, $value): void + { + if (null === $offset) { + $this->data[] = $value; + } else { + $this->data[$offset] = $value; + } + $this->handleReset(); + } + + /** + * Checks if an item exists at the offset. + * + * @param string $offset + */ + public function offsetExists($offset): bool + { + return isset($this->data[$offset]); + } + + /** + * Removes an item at the offset. + * + * @param string $offset + */ + public function offsetUnset($offset): void + { + unset($this->data[$offset]); + $this->handleReset(); + } + + /** + * This is to allow for reset() of a Collection to work properly. + * + * @return void + */ + public function handleReset() + { + $this->first_property = reset($this->data); + } + + /** + * Resets the collection. + */ + public function rewind(): void + { + reset($this->data); + } + + /** + * Gets current collection item. + * + * @return mixed Value + */ + #[\ReturnTypeWillChange] + public function current() + { + return current($this->data); + } + + /** + * Gets current collection key. + * + * @return mixed Value + */ + #[\ReturnTypeWillChange] + public function key() + { + return key($this->data); + } + + /** + * Gets the next collection value. + */ + #[\ReturnTypeWillChange] + public function next(): void + { + next($this->data); + } + + /** + * Checks if the current collection key is valid. + */ + public function valid(): bool + { + $key = key($this->data); + + return null !== $key; + } + + /** + * Gets the size of the collection. + */ + public function count(): int + { + return \count($this->data); + } + + /** + * Gets the item keys. + * + * @return array Collection keys + */ + public function keys(): array + { + return array_keys($this->data); + } + + /** + * Gets the collection data. + * + * @return array Collection data + */ + public function getData(): array + { + return $this->data; + } + + /** + * Sets the collection data. + * + * @param array $data New collection data + */ + public function setData(array $data): void + { + $this->data = $data; + $this->handleReset(); + } + + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->data; + } + + /** + * Removes all items from the collection. + */ + public function clear(): void + { + $this->data = []; + } +} diff --git a/api-rest/vendor/flightphp/core/flight/util/ReturnTypeWillChange.php b/api-rest/vendor/flightphp/core/flight/util/ReturnTypeWillChange.php new file mode 100644 index 0000000..31a929b --- /dev/null +++ b/api-rest/vendor/flightphp/core/flight/util/ReturnTypeWillChange.php @@ -0,0 +1,9 @@ +getConnection(); - -$data = json_decode(file_get_contents("php://input")); - -$email_user = $data->email_user; -$passw_user = $data->passw_user; - -$table_name = 'users'; - -$query = "SELECT id_user, nomb_user, apell_user, passw_user FROM " . $table_name . " WHERE email_user = ? LIMIT 0,1"; - -$stmt = $conn->prepare( $query ); -$stmt->bindParam(1, $email_user); -$stmt->execute(); -$num = $stmt->rowCount(); - -if($num > 0){ - $row = $stmt->fetch(PDO::FETCH_ASSOC); - $id_user = $row['id_user']; - $nomb_user = $row['nomb_user']; - $apell_user = $row['apell_user']; - $passw_db = $row['passw_user']; - - if(password_verify($passw_user, $passw_db)) - { - $secret_key = $env_variables->getKeyJwt(); - $issuer_claim = "localhost"; // this can be the servername - $audience_claim = "THE_AUDIENCE"; - $issuedat_claim = time(); // issued at - $notbefore_claim = $issuedat_claim + 1; //not before in seconds - $expire_claim = $issuedat_claim + 90000; // expire time in seconds - // $now = strtotime("now"); - $token = array( - "iss" => $issuer_claim, - "aud" => $audience_claim, - "iat" => $issuedat_claim, - "nbf" => $notbefore_claim, - "exp" => $expire_claim, - "data" => array( - "id_user" => $id_user, - "nomb_user" => $nomb_user, - "apell_user" => $apell_user, - "email_user" => $email_user - )); - - http_response_code(200); - - $jwt = JWT::encode($token, $secret_key, 'HS256'); - echo json_encode( - array( - "message" => "Inicio de sesión satisfactorio.", - "jwt" => $jwt, - "email" => $email_user, - "expireAt" => $expire_claim - )); - } - else{ - - http_response_code(401); - echo json_encode(array("message" => "Inicio de sesión fallido.")); - } -} -?> \ No newline at end of file diff --git a/api/register.php b/api/register.php deleted file mode 100644 index 1ad4c22..0000000 --- a/api/register.php +++ /dev/null @@ -1,59 +0,0 @@ -getConnection(); - -$data = json_decode(file_get_contents("php://input")); - -$nomb_user = $data->nomb_user; -$apell_user = $data->apell_user; -$nick_user = $data->nick_user; -$email_user = $data->email_user; -$password = $data->password; - -$table_name = 'users'; - -$query = "INSERT INTO " . $table_name . " - SET nomb_user = :nomb_user, - apell_user = :apell_user, - nick_user = :nick_user, - email_user = :email_user, - passw_user = :password"; - -$stmt = $conn->prepare($query); - -$stmt->bindParam(':nomb_user', $nomb_user); -$stmt->bindParam(':apell_user', $apell_user); -$stmt->bindParam(':nick_user', $nick_user); -$stmt->bindParam(':email_user', $email_user); - -$password_hash = password_hash($password, PASSWORD_BCRYPT); - -$stmt->bindParam(':password', $password_hash); - -if($stmt->execute()){ - - http_response_code(200); - echo json_encode(array("message" => "Usuario registrado correctamente.")); -} -else{ - http_response_code(400); - - echo json_encode(array("message" => "Error al registrar usuario.")); -} -?> - diff --git a/composer.json b/composer.json deleted file mode 100644 index cace316..0000000 --- a/composer.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "require": { - "firebase/php-jwt": "^6.10" - } -} diff --git a/config/database.php b/config/database.php deleted file mode 100644 index d544df6..0000000 --- a/config/database.php +++ /dev/null @@ -1,24 +0,0 @@ -connection = null; - - try{ - $this->connection = new PDO("mysql:host=" . $env_variables->getHost() . ";dbname=" . $env_variables->getNamedb(), - $env_variables->getUserDb(), $env_variables->getPasswordDb()); - }catch(PDOException $exception){ - echo "Connection failed: " . $exception->getMessage(); - } - - return $this->connection; - } -} -?> \ No newline at end of file diff --git a/index.php b/index.php deleted file mode 100644 index e69de29..0000000 diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json deleted file mode 100644 index c92f3e4..0000000 --- a/vendor/composer/installed.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "packages": [ - { - "name": "firebase/php-jwt", - "version": "v6.10.0", - "version_normalized": "6.10.0.0", - "source": { - "type": "git", - "url": "https://github.com/firebase/php-jwt.git", - "reference": "a49db6f0a5033aef5143295342f1c95521b075ff" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/a49db6f0a5033aef5143295342f1c95521b075ff", - "reference": "a49db6f0a5033aef5143295342f1c95521b075ff", - "shasum": "" - }, - "require": { - "php": "^7.4||^8.0" - }, - "require-dev": { - "guzzlehttp/guzzle": "^6.5||^7.4", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.5", - "psr/cache": "^1.0||^2.0", - "psr/http-client": "^1.0", - "psr/http-factory": "^1.0" - }, - "suggest": { - "ext-sodium": "Support EdDSA (Ed25519) signatures", - "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" - }, - "time": "2023-12-01T16:26:39+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-4": { - "Firebase\\JWT\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Neuman Vong", - "email": "neuman+pear@twilio.com", - "role": "Developer" - }, - { - "name": "Anant Narayanan", - "email": "anant@php.net", - "role": "Developer" - } - ], - "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", - "homepage": "https://github.com/firebase/php-jwt", - "keywords": [ - "jwt", - "php" - ], - "support": { - "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v6.10.0" - }, - "install-path": "../firebase/php-jwt" - } - ], - "dev": true, - "dev-package-names": [] -} diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php deleted file mode 100644 index 0892c1f..0000000 --- a/vendor/composer/installed.php +++ /dev/null @@ -1,32 +0,0 @@ - array( - 'name' => '__root__', - 'pretty_version' => '1.0.0+no-version-set', - 'version' => '1.0.0.0', - 'reference' => null, - 'type' => 'library', - 'install_path' => __DIR__ . '/../../', - 'aliases' => array(), - 'dev' => true, - ), - 'versions' => array( - '__root__' => array( - 'pretty_version' => '1.0.0+no-version-set', - 'version' => '1.0.0.0', - 'reference' => null, - 'type' => 'library', - 'install_path' => __DIR__ . '/../../', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'firebase/php-jwt' => array( - 'pretty_version' => 'v6.10.0', - 'version' => '6.10.0.0', - 'reference' => 'a49db6f0a5033aef5143295342f1c95521b075ff', - 'type' => 'library', - 'install_path' => __DIR__ . '/../firebase/php-jwt', - 'aliases' => array(), - 'dev_requirement' => false, - ), - ), -); From 87531f39bf6c699e0ba59d61b5b927f9d96f95c6 Mon Sep 17 00:00:00 2001 From: jean Date: Mon, 25 Mar 2024 17:04:04 -0500 Subject: [PATCH 4/7] =?UTF-8?q?Comentarios=20c=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api-rest/config/EnvironmentVariables.php | 1 + api-rest/config/constants.php | 9 ++------- api-rest/controllers/UserController.php | 1 - api-rest/data/DatabaseJsonResponse.php | 22 +++++++++++++++++----- api-rest/data/Queries.php | 1 + api-rest/models/User.php | 2 ++ 6 files changed, 23 insertions(+), 13 deletions(-) diff --git a/api-rest/config/EnvironmentVariables.php b/api-rest/config/EnvironmentVariables.php index 1310b7b..7f20d6d 100644 --- a/api-rest/config/EnvironmentVariables.php +++ b/api-rest/config/EnvironmentVariables.php @@ -1,6 +1,7 @@ envVariables = new EnvironmentVariables(); } + // Función para iniciar sesión, recibe email y contraseña public function loginUser($email, $password) { $query = $this->svDatabase->prepare($this->sqlQueries->queryLogin()); // Obtiene y prepara la consulta declarada en la clase Queries $query->execute([":email_user" => $email]); // Ejecuta la consulta enviando el parametro necesario $rowCount = $query->rowCount(); - if ($rowCount != 0) { + /* Si mi contador de filas obtenidas es diferente de cero verifico + si la contraseña recibida es la misma con la que esta en la db */ + if ($rowCount != 0) { $data = $query->fetch(); $passw_db = $data['passw_user']; - if (password_verify($password, $passw_db)) { + // Metodo password_verify descodifica la contraseña traida de la db y la compara con la recibida + if (password_verify($password, $passw_db)) { $token = array( "iss" => ISS, @@ -48,6 +53,7 @@ public function loginUser($email, $password) { http_response_code(SUCCESS_RESPONSE); + // Codifica el token $jwt = JWT::encode($token, $this->envVariables->getKeyJwt(), $this->envVariables->getAlgJwt()); return array( "message" => "Inicio de sesión satisfactorio.", @@ -63,21 +69,24 @@ public function loginUser($email, $password) { } } + // Función para registrar usuario, recibe el modelo user public function registerUser($user) { $query = $this->svDatabase->prepare($this->sqlQueries->queryRegisterUser()); + // Array de respuesta por defecto con estado de error $array = [ "error" => "Error el registrar usuario.", "status" => "Error" ]; - $passw_hash = password_hash($user->getPasswUser(), PASSWORD_BCRYPT); + $passw_hash = password_hash($user->getPasswUser(), PASSWORD_BCRYPT); // Encripta la contraseña $result = $query->execute([":nomb_user" => $user->getNombUser(), ":apell_user" => $user->getApellUser(), ":nick_user" => $user->getNickUser(), ":email_user" => $user->getEmailUser(), ":passw_user" => $passw_hash]); - + + // Si el resultado es satisfactorio modifica el array de respuesta por mensaje satisfactorio if ($result) { $array = [ @@ -90,9 +99,12 @@ public function registerUser($user) { "status" => "Registro satisfactorio." ]; } - + Flight::json($array); + } + public function protectResources() { + } } diff --git a/api-rest/data/Queries.php b/api-rest/data/Queries.php index d5447ef..698559a 100644 --- a/api-rest/data/Queries.php +++ b/api-rest/data/Queries.php @@ -1,5 +1,6 @@ Date: Wed, 27 Mar 2024 08:22:00 -0500 Subject: [PATCH 5/7] =?UTF-8?q?Cambios=20c=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api-rest/data/DatabaseJsonResponse.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api-rest/data/DatabaseJsonResponse.php b/api-rest/data/DatabaseJsonResponse.php index 73a5be0..b325518 100644 --- a/api-rest/data/DatabaseJsonResponse.php +++ b/api-rest/data/DatabaseJsonResponse.php @@ -88,7 +88,7 @@ public function registerUser($user) { // Si el resultado es satisfactorio modifica el array de respuesta por mensaje satisfactorio if ($result) { - + $array = [ "User" => [ "id" => $this->svDatabase->lastInsertId(), From 76a872ddae829cb00d5a3693f21cbc55ae769819 Mon Sep 17 00:00:00 2001 From: jean Date: Thu, 28 Mar 2024 15:22:06 -0500 Subject: [PATCH 6/7] =?UTF-8?q?Validaci=C3=B3n=20de=20token=20para=20el=20?= =?UTF-8?q?acceso=20a=20los=20servicios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api-rest/api/protected.php | 55 -------------- api-rest/config/constants.php | 1 + api-rest/controllers/UserController.php | 6 +- api-rest/data/DatabaseJsonResponse.php | 96 +++++++++++++++++++------ api-rest/data/Queries.php | 13 ++++ api-rest/routes/api.php | 16 +++++ 6 files changed, 110 insertions(+), 77 deletions(-) delete mode 100644 api-rest/api/protected.php diff --git a/api-rest/api/protected.php b/api-rest/api/protected.php deleted file mode 100644 index eca1d59..0000000 --- a/api-rest/api/protected.php +++ /dev/null @@ -1,55 +0,0 @@ -getConnection(); - -$data = json_decode(file_get_contents("php://input")); - -$authHeader = $_SERVER['HTTP_AUTHORIZATION']; - -$arr = explode(" ", $authHeader); - - -echo json_encode(array( - "message" => "sd" .$arr[1] -)); - -$jwt = $arr[1]; - -if($jwt){ - - try { - - $decoded = JWT::decode($jwt, $secret_key, array('HS256')); - - // Access is granted. Add code of the operation here - - echo json_encode(array( - "message" => "Access granted:", - "error" => $e->getMessage() - )); - - }catch (Exception $e){ - - http_response_code(401); - - echo json_encode(array( - "message" => "Access denied.", - "error" => $e->getMessage() - )); -} - -} -?> \ No newline at end of file diff --git a/api-rest/config/constants.php b/api-rest/config/constants.php index 36574b4..47882e8 100644 --- a/api-rest/config/constants.php +++ b/api-rest/config/constants.php @@ -11,6 +11,7 @@ define('SUCCESS_RESPONSE', 200); define('ACCESS_DENIED', 401); define('BAD_REQUEST', 400); +define('FORBIDDEN', 403); ?> \ No newline at end of file diff --git a/api-rest/controllers/UserController.php b/api-rest/controllers/UserController.php index 4c5b8a8..15d701e 100644 --- a/api-rest/controllers/UserController.php +++ b/api-rest/controllers/UserController.php @@ -7,19 +7,23 @@ class UserController { private $dbJsonResponse; public function __construct() { + $this->dbJsonResponse = new DatabaseJsonResponse(); } public function login($email, $password) { return $this->dbJsonResponse->loginUser($email, $password); - } public function registerUser($user) { return $this->dbJsonResponse->registerUser($user); + } + + public function getUsers($headers) { + return $this->dbJsonResponse->getUsers($headers); } } diff --git a/api-rest/data/DatabaseJsonResponse.php b/api-rest/data/DatabaseJsonResponse.php index b325518..f956972 100644 --- a/api-rest/data/DatabaseJsonResponse.php +++ b/api-rest/data/DatabaseJsonResponse.php @@ -5,8 +5,8 @@ include_once '../api-rest/config/constants.php'; include_once 'Queries.php'; -require 'vendor/autoload.php'; use \Firebase\JWT\JWT; +use \Firebase\JWT\Key; // Clase para retornar los datos traidos de la db en json class DatabaseJsonResponse { @@ -36,7 +36,7 @@ public function loginUser($email, $password) { $passw_db = $data['passw_user']; // Metodo password_verify descodifica la contraseña traida de la db y la compara con la recibida if (password_verify($password, $passw_db)) { - + // Prepara el token con la información requerida $token = array( "iss" => ISS, "aud" => AUD, @@ -44,27 +44,24 @@ public function loginUser($email, $password) { "nbf" => NBF, "exp" => EXP, - "data" => array( + "user" => array( "id" => $data['id_user'], "nombre" => $data['nomb_user'], "apellido" => $data['apell_user'], "email" => $data['email_user'] )); - http_response_code(SUCCESS_RESPONSE); - // Codifica el token $jwt = JWT::encode($token, $this->envVariables->getKeyJwt(), $this->envVariables->getAlgJwt()); return array( "message" => "Inicio de sesión satisfactorio.", - "jwt" => $jwt, - "id" => $data['id_user'] + "token" => $jwt, + "status" => 'OK' ); } else { - http_response_code(ACCESS_DENIED); - - return array("message" => "Inicio de sesión fallido."); + return array("message" => 'Inicio de sesión fallido.', + "status" => 'error'); } } } @@ -90,23 +87,80 @@ public function registerUser($user) { if ($result) { $array = [ - "User" => [ - "id" => $this->svDatabase->lastInsertId(), - "nombre" => $user->getNombUser(), - "apellido" => $user->getApellUser(), - "email" => $user->getEmailUser() - ], - "status" => "Registro satisfactorio." + "message" => 'Registro satisfactorio.', + "status" => 'OK' ]; } - Flight::json($array); + return $array; } - public function protectResources() { - + public function getUsers($headers) { + + if ($this->validateToken($headers)["status"] == "error") { // Validar token recibido (headers) para obtener data + return array("error" => $this->validateToken($headers)["error"], + "status" => 'error'); + } + $query = $this->svDatabase->prepare($this->sqlQueries->queryGetUsers()); + $query->execute(); + $data = $query->fetchAll(); + $users = []; + foreach ($data as $row) { + $users[] = [ + "id" => $row['id_user'], + "nombre" => $row['nomb_user'], + "apellido" => $row['apell_user'], + "nick" => $row['nick_user'], + "email" => $row['email_user'] + ]; + } + + return array("total_users" => $query->rowCount(), + "users" => $users, + "status" => 'OK'); + } -} + // Función para obtener token y retornar decodeado + private function getToken($headers) { + if (isset($headers["Authorization"])) { + $authorization = $headers["Authorization"]; + $authorizationArray = explode(" ", $authorization); + + // Verificar si el token está vacío después de dividirlo + if (empty($authorizationArray[1])) { + return array("error" => 'Unauthenticated request', + "status" => 'error'); + } + + try { + $token = $authorizationArray[1]; // Obtener token + return array("data" => JWT::decode($token, new Key($this->envVariables->getKeyJwt(), $this->envVariables->getAlgJwt())), + "status" => 'OK'); + } catch (\Throwable $th) { + return array("error" => $th->getMessage(), + "status" => 'error'); + } + } else { + return array("error" => 'Unauthenticated request', + "status" => 'error'); + } + } + + // Función para validar token + private function validateToken($headers) { + $token = $this->getToken($headers); + if ($token["status"] == 'OK') { + $query = $this->svDatabase->prepare($this->sqlQueries->queryGetUserById()); + $data = $token["data"]; + $query->execute([":id" => $data->user->id]); + if (!$query->fetchColumn()) { + return array("status" => 'OK'); + } + } + return $token; // en caso de no ser valido retorna el array de error + + } +} ?> \ No newline at end of file diff --git a/api-rest/data/Queries.php b/api-rest/data/Queries.php index 698559a..fe30911 100644 --- a/api-rest/data/Queries.php +++ b/api-rest/data/Queries.php @@ -20,6 +20,19 @@ public function queryRegisterUser() { return $query; } + + public function queryGetUserById() { + $query = "SELECT id_user, nomb_user, apell_user, email_user + FROM users WHERE id_user = :id"; + + return $query; + } + + public function queryGetUsers() { + $query = "SELECT id_user, nomb_user, apell_user, nick_user, email_user FROM users"; + + return $query; + } } ?> \ No newline at end of file diff --git a/api-rest/routes/api.php b/api-rest/routes/api.php index 0535720..621a930 100644 --- a/api-rest/routes/api.php +++ b/api-rest/routes/api.php @@ -10,6 +10,7 @@ function getUserController() { return $userController = new UserController(); } +// Recibe json con el email y contraseña Flight::route('POST /login', function () { $data = Flight::request()->data; // Obtener los datos JSON del cuerpo de la solicitud @@ -24,6 +25,7 @@ function getUserController() { } }); +// Recibe json con los datos del usuario a registrar Flight::route('POST /registerUser', function () { $data = Flight::request()->data; @@ -42,6 +44,20 @@ function getUserController() { }); +Flight::route('GET /getUsers', function() { + + $headers = apache_request_headers(); // Obtener encabezado con el token authorization + + $response = getUserController()->getUsers($headers); // Obtener respuesta de la capa de datos + + if ($response["status"] == 'error' ) { // Si el estado es error muestra el mensaje del error que se produjo + Flight::halt(FORBIDDEN, $response["error"]); + } else { + Flight::json($response); // Si el estado es OK devuelve la lista de usuarios + } + +}); + Flight::start(); ?> \ No newline at end of file From 708700d144656f0f2097de4a0045df21688b9342 Mon Sep 17 00:00:00 2001 From: jean Date: Mon, 1 Apr 2024 23:28:45 -0500 Subject: [PATCH 7/7] =?UTF-8?q?Limpiando=20c=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api-rest/index.php | 2 +- .../{ => src}/config/EnvironmentVariables.php | 0 api-rest/{ => src}/config/constants.php | 0 .../{ => src}/controllers/UserController.php | 2 +- api-rest/{ => src}/data/DatabaseConexion.php | 2 +- .../{ => src}/data/DatabaseJsonResponse.php | 49 ++++++++++--------- api-rest/{ => src}/data/Queries.php | 0 api-rest/{ => src}/models/User.php | 0 api-rest/{ => src}/routes/api.php | 6 +-- 9 files changed, 33 insertions(+), 28 deletions(-) rename api-rest/{ => src}/config/EnvironmentVariables.php (100%) rename api-rest/{ => src}/config/constants.php (100%) rename api-rest/{ => src}/controllers/UserController.php (84%) rename api-rest/{ => src}/data/DatabaseConexion.php (91%) rename api-rest/{ => src}/data/DatabaseJsonResponse.php (82%) rename api-rest/{ => src}/data/Queries.php (100%) rename api-rest/{ => src}/models/User.php (100%) rename api-rest/{ => src}/routes/api.php (87%) diff --git a/api-rest/index.php b/api-rest/index.php index 19d9b54..e0e12f9 100644 --- a/api-rest/index.php +++ b/api-rest/index.php @@ -1,5 +1,5 @@ \ No newline at end of file diff --git a/api-rest/config/EnvironmentVariables.php b/api-rest/src/config/EnvironmentVariables.php similarity index 100% rename from api-rest/config/EnvironmentVariables.php rename to api-rest/src/config/EnvironmentVariables.php diff --git a/api-rest/config/constants.php b/api-rest/src/config/constants.php similarity index 100% rename from api-rest/config/constants.php rename to api-rest/src/config/constants.php diff --git a/api-rest/controllers/UserController.php b/api-rest/src/controllers/UserController.php similarity index 84% rename from api-rest/controllers/UserController.php rename to api-rest/src/controllers/UserController.php index 15d701e..3588362 100644 --- a/api-rest/controllers/UserController.php +++ b/api-rest/src/controllers/UserController.php @@ -1,6 +1,6 @@ fetch(); - $passw_db = $data['passw_user']; + $dataUser = $query->fetch(); + $passw_db = $dataUser['passw_user']; // Metodo password_verify descodifica la contraseña traida de la db y la compara con la recibida if (password_verify($password, $passw_db)) { - // Prepara el token con la información requerida - $token = array( - "iss" => ISS, - "aud" => AUD, - "iat" => IAT, - "nbf" => NBF, - "exp" => EXP, - - "user" => array( - "id" => $data['id_user'], - "nombre" => $data['nomb_user'], - "apellido" => $data['apell_user'], - "email" => $data['email_user'] - )); - - // Codifica el token - $jwt = JWT::encode($token, $this->envVariables->getKeyJwt(), $this->envVariables->getAlgJwt()); + + $token = $this->buildToken($dataUser); // Prepara el token pasando el usuario($dataUser) + $jwt = JWT::encode($token, $this->envVariables->getKeyJwt(), $this->envVariables->getAlgJwt()); // Codifica el token + return array( "message" => "Inicio de sesión satisfactorio.", "token" => $jwt, @@ -60,7 +47,7 @@ public function loginUser($email, $password) { ); } else { - return array("message" => 'Inicio de sesión fallido.', + return array("message" => 'Contraseña Incorrecta.', "status" => 'error'); } } @@ -121,6 +108,24 @@ public function getUsers($headers) { } + // Construye y retorna el token con la información y el usuario($data) requeridos + private function buildToken($dataUser) { + + return array( + "iss" => ISS, + "aud" => AUD, + "iat" => IAT, + "nbf" => NBF, + "exp" => EXP, + + "user" => array( + "id" => $dataUser['id_user'], + "nombre" => $dataUser['nomb_user'], + "apellido" => $dataUser['apell_user'], + "email" => $dataUser['email_user'] + )); + } + // Función para obtener token y retornar decodeado private function getToken($headers) { if (isset($headers["Authorization"])) { diff --git a/api-rest/data/Queries.php b/api-rest/src/data/Queries.php similarity index 100% rename from api-rest/data/Queries.php rename to api-rest/src/data/Queries.php diff --git a/api-rest/models/User.php b/api-rest/src/models/User.php similarity index 100% rename from api-rest/models/User.php rename to api-rest/src/models/User.php diff --git a/api-rest/routes/api.php b/api-rest/src/routes/api.php similarity index 87% rename from api-rest/routes/api.php rename to api-rest/src/routes/api.php index 621a930..490ff21 100644 --- a/api-rest/routes/api.php +++ b/api-rest/src/routes/api.php @@ -1,8 +1,8 @@