From 9d42c23873d57b9025de3fd434cb214232da4aea Mon Sep 17 00:00:00 2001 From: nuxsmin Date: Sun, 15 Jul 2018 14:05:15 +0200 Subject: [PATCH] * [ADD] Unit testing. Work in progress * [MOD] Request handling rewritten to make it testable * [MOD] Code refactoring --- app/modules/api/Init.php | 2 +- .../web/Controllers/ControllerBase.php | 3 +- .../web/Controllers/Helpers/LayoutHelper.php | 3 +- .../web/Controllers/LoginController.php | 7 +- .../Controllers/UserPassResetController.php | 7 +- app/modules/web/Init.php | 8 +- lib/BaseFunctions.php | 18 + lib/Definitions.php | 1 + lib/SP/Bootstrap.php | 13 +- lib/SP/Core/Crypt/Cookie.php | 13 +- lib/SP/Core/Crypt/SecureKeyCookie.php | 31 +- lib/SP/Core/Crypt/UUIDCookie.php | 26 +- lib/SP/Core/Language.php | 13 +- lib/SP/Core/ModuleBase.php | 3 +- lib/SP/Http/Request.php | 404 +++++++++++------- lib/SP/Providers/Mail/MailHandler.php | 11 +- lib/SP/Services/Api/ApiService.php | 3 +- lib/SP/Services/Auth/LoginService.php | 2 +- .../Services/Crypt/SecureSessionService.php | 46 +- lib/SP/Services/EventLog/EventlogService.php | 9 +- .../Services/PublicLink/PublicLinkService.php | 21 +- lib/SP/Services/Track/TrackService.php | 35 +- lib/SP/Util/Checks.php | 40 -- lib/SP/Util/HttpUtil.php | 150 ++----- lib/SP/Util/Util.php | 15 +- .../Repositories/PublicLinkRepositoryTest.php | 11 +- .../Crypt/SecureSessionServiceTest.php | 59 +++ 27 files changed, 541 insertions(+), 413 deletions(-) create mode 100644 tests/Services/Crypt/SecureSessionServiceTest.php diff --git a/app/modules/api/Init.php b/app/modules/api/Init.php index ea5643ac..8e0554c3 100644 --- a/app/modules/api/Init.php +++ b/app/modules/api/Init.php @@ -91,7 +91,7 @@ class Init extends ModuleBase $this->language->setLanguage(); // Checks if it needs to switch the request over HTTPS - HttpUtil::checkHttps($this->configData); + HttpUtil::checkHttps($this->configData, $this->request); // Checks if sysPass is installed if (!$this->checkInstalled()) { diff --git a/app/modules/web/Controllers/ControllerBase.php b/app/modules/web/Controllers/ControllerBase.php index f3155c7c..96088b25 100644 --- a/app/modules/web/Controllers/ControllerBase.php +++ b/app/modules/web/Controllers/ControllerBase.php @@ -45,7 +45,6 @@ use SP\Mvc\View\Template; use SP\Providers\Auth\Browser\Browser; use SP\Services\Auth\AuthException; use SP\Services\User\UserLoginResponse; -use SP\Util\Checks; /** * Clase base para los controladores @@ -157,7 +156,7 @@ abstract class ControllerBase $this->view->setBase(strtolower($this->controllerName)); - $this->isAjax = Checks::isAjax($this->router); + $this->isAjax = $this->request->isAjax(); if ($this->session->isLoggedIn()) { $this->userData = clone $this->session->getUserData(); diff --git a/app/modules/web/Controllers/Helpers/LayoutHelper.php b/app/modules/web/Controllers/Helpers/LayoutHelper.php index 1c7319d6..b04fb6cc 100644 --- a/app/modules/web/Controllers/Helpers/LayoutHelper.php +++ b/app/modules/web/Controllers/Helpers/LayoutHelper.php @@ -36,7 +36,6 @@ use SP\Core\UI\ThemeInterface; use SP\Html\DataGrid\DataGridAction; use SP\Http\Uri; use SP\Services\Install\Installer; -use SP\Util\Checks; use SP\Util\Util; /** @@ -109,7 +108,7 @@ class LayoutHelper extends HelperBase $this->view->assign('logoNoText', Bootstrap::$WEBURI . '/public/images/logo_icon.svg'); $this->view->assign('logo', Bootstrap::$WEBURI . '/public/images/logo_full_bg.png'); $this->view->assign('logonobg', Bootstrap::$WEBURI . '/public/images/logo_full_nobg.png'); - $this->view->assign('httpsEnabled', Checks::httpsEnabled()); + $this->view->assign('httpsEnabled', $this->request->isHttps()); $this->view->assign('homeRoute', Acl::getActionRoute(ActionsInterface::ACCOUNT)); $this->loggedIn = $this->context->isLoggedIn(); diff --git a/app/modules/web/Controllers/LoginController.php b/app/modules/web/Controllers/LoginController.php index 71a5edd7..9fc1927e 100644 --- a/app/modules/web/Controllers/LoginController.php +++ b/app/modules/web/Controllers/LoginController.php @@ -30,7 +30,6 @@ use SP\Core\Events\Event; use SP\Core\Events\EventMessage; use SP\Core\Exceptions\SPException; use SP\Core\SessionUtil; -use SP\Http\Request; use SP\Modules\Web\Controllers\Helpers\LayoutHelper; use SP\Modules\Web\Controllers\Traits\JsonTrait; use SP\Services\Auth\LoginService; @@ -69,12 +68,12 @@ class LoginController extends ControllerBase $loginResponmse = $loginService->doLogin(); - $forward = Request::getRequestHeaders('X-Forwarded-For'); + $forward = $this->request->getForwardedFor(); - if ($forward) { + if ($forward !== null) { $this->eventDispatcher->notifyEvent('login.info', new Event($this, EventMessage::factory() - ->addDetail('X-Forwarded-For', $this->configData->isDemoEnabled() ? '***' : $forward)) + ->addDetail('Forwarded', $this->configData->isDemoEnabled() ? '***' : implode(',', $forward))) ); } diff --git a/app/modules/web/Controllers/UserPassResetController.php b/app/modules/web/Controllers/UserPassResetController.php index 02445332..5402f581 100644 --- a/app/modules/web/Controllers/UserPassResetController.php +++ b/app/modules/web/Controllers/UserPassResetController.php @@ -2,8 +2,8 @@ /** * sysPass * - * @author nuxsmin - * @link https://syspass.org + * @author nuxsmin + * @link https://syspass.org * @copyright 2012-2018, Rubén Domínguez nuxsmin@$syspass.org * * This file is part of sysPass. @@ -29,6 +29,7 @@ use SP\Core\Events\EventMessage; use SP\Core\Exceptions\SPException; use SP\Core\Exceptions\ValidationException; use SP\Http\JsonResponse; +use SP\Http\Request; use SP\Modules\Web\Controllers\Helpers\LayoutHelper; use SP\Modules\Web\Controllers\Traits\JsonTrait; use SP\Repositories\Track\TrackRequest; @@ -203,6 +204,6 @@ class UserPassResetController extends ControllerBase protected function initialize() { $this->trackService = $this->dic->get(TrackService::class); - $this->trackRequest = TrackService::getTrackRequest('userPassReset'); + $this->trackRequest = TrackService::getTrackRequest('userPassReset', $this->dic->get(Request::class)); } } \ No newline at end of file diff --git a/app/modules/web/Init.php b/app/modules/web/Init.php index b7a17ffc..5f330771 100644 --- a/app/modules/web/Init.php +++ b/app/modules/web/Init.php @@ -32,6 +32,7 @@ use SP\Core\Context\ContextInterface; use SP\Core\Context\SessionContext; use SP\Core\Crypt\CryptSessionHandler; use SP\Core\Crypt\Session as CryptSession; +use SP\Core\Crypt\UUIDCookie; use SP\Core\Language; use SP\Core\ModuleBase; use SP\Core\UI\Theme; @@ -78,6 +79,7 @@ class Init extends ModuleBase * Init constructor. * * @param Container $container + * * @throws \DI\DependencyException * @throws \DI\NotFoundException */ @@ -95,6 +97,7 @@ class Init extends ModuleBase * Initialize Web App * * @param string $controller + * * @throws \DI\DependencyException * @throws \DI\NotFoundException * @throws \SP\Core\Exceptions\SPException @@ -133,7 +136,7 @@ class Init extends ModuleBase } // Comprobar si es necesario cambiar a HTTPS - HttpUtil::checkHttps($this->configData); + HttpUtil::checkHttps($this->configData, $this->request); if (in_array($controller, self::PARTIAL_INIT, true) === false) { // Checks if sysPass is installed @@ -195,13 +198,14 @@ class Init extends ModuleBase * Iniciar la sesión PHP * * @param bool $encrypt Encriptar la sesión de PHP + * * @throws ContextException */ private function initSession($encrypt = false) { if ($encrypt === true && Bootstrap::$checkPhpVersion - && ($key = $this->secureSessionService->getKey()) !== false) { + && ($key = $this->secureSessionService->getKey(UUIDCookie::factory($this->request))) !== false) { session_set_save_handler(new CryptSessionHandler($key), true); } diff --git a/lib/BaseFunctions.php b/lib/BaseFunctions.php index c638075f..6699f61e 100644 --- a/lib/BaseFunctions.php +++ b/lib/BaseFunctions.php @@ -68,6 +68,7 @@ function processException(\Exception $exception) /** * @param $trace + * * @return string */ function formatTrace($trace) @@ -92,6 +93,7 @@ function formatTrace($trace) * * @param string $message * @param bool $translate Si es necesario traducir + * * @return string */ function __($message, $translate = true) @@ -103,6 +105,7 @@ function __($message, $translate = true) * Returns an untranslated string (gettext placeholder) * * @param string $message + * * @return string */ function __u($message) @@ -116,6 +119,7 @@ function __u($message) * @param string $domain * @param string $message * @param bool $translate + * * @return string */ function _t($domain, $message, $translate = true) @@ -127,6 +131,7 @@ function _t($domain, $message, $translate = true) * Capitalización de cadenas multi byte * * @param $string + * * @return string */ function mb_ucfirst($string) @@ -139,6 +144,7 @@ function mb_ucfirst($string) * Esta función se utiliza para calcular el tiempo de renderizado con coma flotante * * @param float $from + * * @returns float con el tiempo actual * @return float */ @@ -174,6 +180,7 @@ function initModule($module) /** * @param $dir * @param $levels + * * @return bool|string */ function nDirname($dir, $levels) @@ -189,6 +196,7 @@ function nDirname($dir, $levels) /** * @param Exception $exception + * * @throws ReflectionException */ function flattenExceptionBacktrace(\Exception $exception) @@ -219,4 +227,14 @@ function flattenExceptionBacktrace(\Exception $exception) $traceProperty->setAccessible(false); } +/** + * Prints a fancy trace info using Xdebug extension + */ +function printTraceInfo() +{ + if (DEBUG && extension_loaded('xdebug')) { + xdebug_print_function_stack(); + } +} + //set_exception_handler('\flattenExceptionBacktrace'); \ No newline at end of file diff --git a/lib/Definitions.php b/lib/Definitions.php index 7c9e1788..28ddca75 100644 --- a/lib/Definitions.php +++ b/lib/Definitions.php @@ -27,6 +27,7 @@ use function DI\object; return [ \Klein\Klein::class => object(\Klein\Klein::class), + \SP\Http\Request::class => object(\SP\Http\Request::class)->constructor(\Klein\Request::createFromGlobals()), \SP\Core\Context\ContextInterface::class => function (\Interop\Container\ContainerInterface $c) { switch (APP_MODULE) { case 'web': diff --git a/lib/SP/Bootstrap.php b/lib/SP/Bootstrap.php index e40ad99a..436f4b8b 100644 --- a/lib/SP/Bootstrap.php +++ b/lib/SP/Bootstrap.php @@ -38,6 +38,7 @@ use SP\Core\Exceptions\ConfigException; use SP\Core\Exceptions\InitializationException; use SP\Core\Language; use SP\Core\UI\Theme; +use SP\Http\Request; use SP\Modules\Api\Init as InitApi; use SP\Modules\Web\Init as InitWeb; use SP\Services\Api\ApiRequest; @@ -45,7 +46,6 @@ use SP\Services\Api\JsonRpcResponse; use SP\Services\Upgrade\UpgradeConfigService; use SP\Services\Upgrade\UpgradeUtil; use SP\Util\Checks; -use SP\Util\HttpUtil; use SP\Util\Util; defined('APP_ROOT') || die(); @@ -105,6 +105,10 @@ class Bootstrap * @var Language */ protected $language; + /** + * @var Request + */ + protected $request; /** * @var Config */ @@ -129,6 +133,7 @@ class Bootstrap $this->config = $container->get(Config::class); $this->configData = $this->config->getConfigData(); $this->router = $container->get(Klein::class); + $this->request = $container->get(Request::class); $this->language = $container->get(Language::class); $this->initRouter(); @@ -380,7 +385,7 @@ class Bootstrap self::$WEBROOT = '/' . self::$WEBROOT; } - self::$WEBURI = HttpUtil::getHttpHost() . self::$WEBROOT; + self::$WEBURI = $this->request->getHttpHost() . self::$WEBROOT; } /** @@ -444,13 +449,13 @@ class Bootstrap debugLog('------------'); debugLog('Boostrap:web'); - $bs->router->dispatch(); + $bs->router->dispatch($bs->request->getRequest()); break; case 'api': debugLog('------------'); debugLog('Boostrap:api'); - $bs->router->dispatch(); + $bs->router->dispatch($bs->request->getRequest()); break; default; throw new InitializationException('Unknown module'); diff --git a/lib/SP/Core/Crypt/Cookie.php b/lib/SP/Core/Crypt/Cookie.php index c2b2a7bd..862a0fa0 100644 --- a/lib/SP/Core/Crypt/Cookie.php +++ b/lib/SP/Core/Crypt/Cookie.php @@ -25,6 +25,7 @@ namespace SP\Core\Crypt; use SP\Bootstrap; +use SP\Http\Request; /** * Class Cookie @@ -37,15 +38,21 @@ abstract class Cookie * @var string */ private $cookieName; + /** + * @var Request + */ + protected $request; /** * Cookie constructor. * - * @param string $cookieName + * @param string $cookieName + * @param Request $request */ - public function __construct($cookieName) + protected function __construct($cookieName, Request $request) { $this->cookieName = $cookieName; + $this->request = $request; } /** @@ -87,7 +94,7 @@ abstract class Cookie */ protected function getCookie() { - return isset($_COOKIE[$this->cookieName]) ? $_COOKIE[$this->cookieName] : false; + return $this->request->getRequest()->cookies()->get($this->cookieName, false); } /** diff --git a/lib/SP/Core/Crypt/SecureKeyCookie.php b/lib/SP/Core/Crypt/SecureKeyCookie.php index 02498e39..0d0b930b 100644 --- a/lib/SP/Core/Crypt/SecureKeyCookie.php +++ b/lib/SP/Core/Crypt/SecureKeyCookie.php @@ -27,7 +27,6 @@ namespace SP\Core\Crypt; use Defuse\Crypto\Exception\CryptoException; use Defuse\Crypto\Key; use SP\Http\Request; -use SP\Util\HttpUtil; /** * Class SecureKeyCookie @@ -47,24 +46,31 @@ class SecureKeyCookie extends Cookie */ protected $securedKey; + /** + * @param Request $request + * + * @return SecureKeyCookie + */ + public static function factory(Request $request) { + return new self(self::COOKIE_NAME, $request); + } + /** * Obtener una llave de encriptación * * @return Key|false|string */ - public static function getKey() + public function getKey() { - $secureKeyCookie = new self(self::COOKIE_NAME); + $key = $this->getCypher(); - $key = $secureKeyCookie->getCypher(); - - if (($cookie = $secureKeyCookie->getCookie())) { - $data = $secureKeyCookie->getCookieData($cookie, $key); + if (($cookie = $this->getCookie())) { + $data = $this->getCookieData($cookie, $key); if ($data === false) { debugLog('Cookie verification error.'); - return $secureKeyCookie->saveKey($key); + return $this->saveKey($key); } /** @var Vault $vault */ @@ -81,10 +87,10 @@ class SecureKeyCookie extends Cookie return false; } } - } elseif (($secureKeyCookie->getSecuredKey() instanceof Key) === true) { - return $secureKeyCookie->getSecuredKey(); + } elseif (($this->getSecuredKey() instanceof Key) === true) { + return $this->getSecuredKey(); } else { - return $secureKeyCookie->saveKey($key); + return $this->saveKey($key); } return false; @@ -97,13 +103,14 @@ class SecureKeyCookie extends Cookie */ private function getCypher() { - return md5(Request::getRequestHeaders('User-Agent') . HttpUtil::getClientAddress()); + return md5($this->request->getHeader('User-Agent') . $this->request->getClientAddress()); } /** * Guardar una llave de encriptación * * @param $key + * * @return Key|false */ public function saveKey($key) diff --git a/lib/SP/Core/Crypt/UUIDCookie.php b/lib/SP/Core/Crypt/UUIDCookie.php index f64c3ba5..ea43d747 100644 --- a/lib/SP/Core/Crypt/UUIDCookie.php +++ b/lib/SP/Core/Crypt/UUIDCookie.php @@ -24,6 +24,8 @@ namespace SP\Core\Crypt; +use SP\Http\Request; + /** * Class SecureCookie * @@ -36,18 +38,28 @@ class UUIDCookie extends Cookie */ const COOKIE_NAME = 'SYSPASS_UUID'; + /** + * @param Request $request + * + * @return UUIDCookie + */ + public static function factory(Request $request) + { + return new self(self::COOKIE_NAME, $request); + } + /** * Creates a cookie and sets its data * * @param string $signKey Signing key + * * @return string */ - public static function createCookie($signKey) + public function createCookie($signKey) { $uuid = uniqid('', true); - $cookie = new self(self::COOKIE_NAME); - if ($cookie->setCookie($cookie->sign($uuid, $signKey))) { + if ($this->setCookie($this->sign($uuid, $signKey))) { return $uuid; } @@ -58,13 +70,13 @@ class UUIDCookie extends Cookie * Loads cookie data * * @param string $signKey Signing key + * * @return bool|string */ - public static function loadCookie($signKey) + public function loadCookie($signKey) { - $cookie = new self(self::COOKIE_NAME); - $data = $cookie->getCookie(); + $data = $this->getCookie(); - return $data !== false ? $cookie->getCookieData($data, $signKey) : false; + return $data !== false ? $this->getCookieData($data, $signKey) : false; } } \ No newline at end of file diff --git a/lib/SP/Core/Language.php b/lib/SP/Core/Language.php index c71ebaee..6ef3ecc5 100644 --- a/lib/SP/Core/Language.php +++ b/lib/SP/Core/Language.php @@ -88,17 +88,23 @@ class Language * @var SessionContext */ protected $context; + /** + * @var Request + */ + private $request; /** * Language constructor. * * @param ContextInterface $session * @param Config $config + * @param Request $request */ - public function __construct(ContextInterface $session, Config $config) + public function __construct(ContextInterface $session, Config $config, Request $request) { $this->context = $session; $this->configData = $config->getConfigData(); + $this->request = $request; ksort(self::$langs); } @@ -175,15 +181,16 @@ class Language */ private function getBrowserLang() { - $lang = Request::getRequestHeaders('HTTP_ACCEPT_LANGUAGE'); + $lang = $this->request->getHeader('HTTP_ACCEPT_LANGUAGE'); - return $lang ? str_replace('-', '_', substr($lang, 0, 5)) : ''; + return !empty($lang) ? str_replace('-', '_', substr($lang, 0, 5)) : ''; } /** * Comprobar si el archivo de lenguaje existe * * @param string $lang El lenguaje a comprobar + * * @return bool */ private function checkLangFile($lang) diff --git a/lib/SP/Core/ModuleBase.php b/lib/SP/Core/ModuleBase.php index e4c7cf28..8f89c5bc 100644 --- a/lib/SP/Core/ModuleBase.php +++ b/lib/SP/Core/ModuleBase.php @@ -37,7 +37,6 @@ use SP\Providers\Log\RemoteSyslogHandler; use SP\Providers\Log\SyslogHandler; use SP\Providers\Mail\MailHandler; use SP\Providers\Notification\NotificationHandler; -use SP\Util\Checks; use SP\Util\Util; /** @@ -106,7 +105,7 @@ abstract class ModuleBase if ($this->configData->isMaintenance()) { Bootstrap::$LOCK = Util::getAppLock(); - return (Checks::isAjax($this->router) + return ($this->request->isAjax() || (Bootstrap::$LOCK !== false && Bootstrap::$LOCK->userId > 0 && $context->isLoggedIn() diff --git a/lib/SP/Http/Request.php b/lib/SP/Http/Request.php index 01449846..53fd135c 100644 --- a/lib/SP/Http/Request.php +++ b/lib/SP/Http/Request.php @@ -25,12 +25,10 @@ namespace SP\Http; use Klein\DataCollection\DataCollection; -use Klein\Klein; use SP\Bootstrap; use SP\Core\Crypt\CryptPKI; use SP\Core\Crypt\Hash; use SP\Core\Exceptions\SPException; -use SP\Html\Html; use SP\Util\Filter; use SP\Util\Util; @@ -45,6 +43,10 @@ class Request * @var array Directorios seguros para include */ const SECURE_DIRS = ['css', 'js']; + /** + * @var \Klein\DataCollection\HeaderDataCollection + */ + protected $headers; /** * @var \Klein\Request */ @@ -53,16 +55,26 @@ class Request * @var DataCollection */ private $params; + /** + * @var string + */ + private $method; + /** + * @var bool + */ + private $https; /** * Request constructor. * - * @param Klein $klein + * @param \Klein\Request $request */ - public function __construct(Klein $klein) + public function __construct(\Klein\Request $request) { - $this->request = $klein->request(); + $this->request = $request; + $this->headers = $this->request->headers(); $this->params = $this->getParamsByMethod(); + $this->detectHttps(); } /** @@ -71,147 +83,32 @@ class Request private function getParamsByMethod() { if ($this->request->method('GET')) { + $this->method = 'GET'; return $this->request->paramsGet(); } else { + $this->method = 'POST'; return $this->request->paramsPost(); } } /** - * Devolver las cabeceras enviadas desde el cliente. - * - * @param string $header nombre de la cabecera a devolver - * - * @return array|string + * Detects if the connection is done through HTTPS */ - public static function getRequestHeaders($header = '') + private function detectHttps() { - if (!empty($header)) { - $header = strpos($header, 'HTTP_') === false ? 'HTTP_' . str_replace('-', '_', strtoupper($header)) : $header; - - return isset($_SERVER[$header]) ? $_SERVER[$header] : ''; - } - - return self::getApacheHeaders(); - } - - /** - * Función que sustituye a apache_request_headers - * - * @return array - */ - private static function getApacheHeaders() - { - if (function_exists('\apache_request_headers')) { - return apache_request_headers(); - } - - $headers = []; - - foreach ($_SERVER as $key => $value) { - if (strpos($key, 'HTTP_') === 0) { - $key = ucwords(strtolower(str_replace('_', '-', substr($key, 5))), '-'); - $headers[$key] = $value; - } else { - $headers[$key] = $value; - } - } - - return $headers; - } - - /** - * Obtener los valores de variables $_GET y $_POST - * y devolverlos limpios con el tipo correcto o esperado. - * - * @param string $param con el parámetro a consultar - * @param mixed $default valor por defecto a devolver - * @param bool $check comprobar si el parámetro está presente - * @param mixed $force valor devuelto si el parámeto está definido - * @param bool $sanitize escapar/eliminar carácteres especiales - * - * @return mixed si está presente el parámeto en la petición devuelve bool. Si lo está, devuelve el valor. - * @deprecated - */ - public static function analyze($param, $default = '', $check = false, $force = false, $sanitize = true) - { - if (!isset($_REQUEST[$param])) { - return $force ? !$force : $default; - } - - if ($check) { - return true; - } - - if ($force) { - return $force; - } - - return self::parse($_REQUEST[$param], $default, $sanitize); - } - - /** - * Devolver el valor con el tipo correcto o requerido. - * - * @param $value mixed valor a analizar - * @param $default mixed tipo por defecto a devolver - * @param $sanitize bool limpiar una cadena de caracteres - * - * @return mixed - * @deprecated - */ - public static function parse(&$value, $default, $sanitize) - { - if (is_array($value)) { - foreach ($value as &$data) { - $data = self::parse($data, $default, $sanitize); - } - - return $value; - } - - if ((is_numeric($value) || is_numeric($default)) - && !is_string($default) - ) { - return (int)$value; - } - - if (is_string($value) - ) { - return ($sanitize === true) ? Html::sanitize($value) : (string)$value; - } - - return $value; - } - - /** - * Comprobar si existen parámetros pasados por POST para enviarlos por GET - */ - public static function importUrlParamsToGet() - { - $params = []; - - foreach ($_REQUEST as $param => $value) { - Html::sanitize($param); - Html::sanitize($value); - - if (strpos($param, 'g_') !== false) { - $params[] = substr($param, 2) . '=' . $value; - } - } - - return count($params) > 0 ? '?' . implode('&', $params) : ''; + $this->https = ($this->request->server()->exists('HTTPS') && $this->request->server()->get('HTTPS') !== 'off') + || $this->request->server()->get('SERVER_PORT', 0) === 443; } /** * Devuelve un nombre de archivo seguro * - * @param $file - * @param null $base + * @param string $file + * @param string $base * * @return string */ - public static function getSecureAppFile($file, $base = null) + public static function getSecureAppFile(string $file, string $base = null) { return basename(self::getSecureAppPath($file, $base)); } @@ -219,12 +116,12 @@ class Request /** * Devolver una ruta segura para * - * @param $path + * @param string $path * @param string $base * * @return string */ - public static function getSecureAppPath($path, $base = null) + public static function getSecureAppPath(string $path, string $base = null) { if ($base === null) { $base = APP_ROOT; @@ -243,6 +140,54 @@ class Request return $realPath; } + /** + * @param bool $fullForwarded + * + * @return array|array[]|mixed|string + */ + public function getClientAddress(bool $fullForwarded = false) + { + if (APP_MODULE === 'tests') { + return '127.0.0.1'; + } + + if (($forwarded = $this->getForwardedFor()) !== null) { + return $fullForwarded ? implode(',', $forwarded) : $forwarded[0]; + } + + return $this->request->server()->get('REMOTE_ADDR', ''); + } + + /** + * @return string[]|null + */ + public function getForwardedFor() + { + // eg: Forwarded: by=; for=; host=; proto= + if (($forwarded = $this->headers->get('HTTP_FORWARDED')) !== null && + preg_match_all('/(?:for=([\w.:]+))|(?:for="\[([\w.:]+)\]")/i', + $forwarded, $matches) + ) { + return array_filter(array_merge($matches[1], $matches[2]), function ($value) { + return !empty($value); + }); + } + + // eg: X-Forwarded-For: 192.0.2.43, 2001:db8:cafe::17 + if (($xForwarded = $this->headers->exists('HTTP_X_FORWARDED_FOR')) !== null) { + $matches = preg_split('/(?<=[\w])+,\s?/i', + $xForwarded, + -1, + PREG_SPLIT_NO_EMPTY); + + if (count($matches) > 0) { + return $matches; + } + } + + return null; + } + /** * Comprobar si se realiza una recarga de la página * @@ -250,17 +195,17 @@ class Request */ public function checkReload() { - return $this->request->headers()->get('Cache-Control') === 'max-age=0'; + return $this->headers->get('Cache-Control') === 'max-age=0'; } /** - * @param $param - * @param $default + * @param string $param + * @param string $default * * @return string * @deprecated */ - public function analyzeEmail($param, $default = null) + public function analyzeEmail(string $param, string $default = null) { if (!$this->params->exists($param)) { return $default; @@ -272,11 +217,11 @@ class Request /** * Analizar un valor encriptado y devolverlo desencriptado * - * @param $param + * @param string $param * * @return string */ - public function analyzeEncrypted($param) + public function analyzeEncrypted(string $param) { $encryptedData = $this->analyzeString($param); @@ -310,7 +255,7 @@ class Request * * @return string */ - public function analyzeString($param, $default = null) + public function analyzeString(string $param, string $default = null) { if (!$this->params->exists($param)) { return $default; @@ -326,38 +271,25 @@ class Request * * @return mixed */ - public function analyzeArray($param, callable $mapper = null, $default = null) + public function analyzeArray(string $param, callable $mapper = null, $default = null) { - if ($this->params->exists($param) - && is_array($this->params->get($param)) + $requestValue = $this->params->get($param); + + if ($requestValue !== null + && is_array($requestValue) ) { if (is_callable($mapper)) { - return $mapper($this->params->get($param)); + return $mapper($requestValue); } return array_map(function ($value) { return is_numeric($value) ? Filter::getInt($value) : Filter::getString($value); - }, $this->params->get($param)); + }, $requestValue); } return $default; } - /** - * @param $param - * @param $default - * - * @return int - */ - public function analyzeInt($param, $default = null): int - { - if (!$this->params->exists($param)) { - return (int)$default; - } - - return Filter::getInt($this->params->get($param)); - } - /** * Comprobar si la petición es en formato JSON * @@ -365,7 +297,7 @@ class Request */ public function isJson() { - return strpos($this->request->headers()->get('Accept'), 'application/json') !== false; + return strpos($this->headers->get('Accept'), 'application/json') !== false; } /** @@ -375,10 +307,25 @@ class Request */ public function isAjax() { - return $this->request->headers()->get('X-Requested-With') === 'XMLHttpRequest' + return $this->headers->get('X-Requested-With') === 'XMLHttpRequest' || $this->analyzeInt('isAjax', 0) === 1; } + /** + * @param string $param + * @param int $default + * + * @return int + */ + public function analyzeInt(string $param, int $default = null): int + { + if (!$this->params->exists($param)) { + return (int)$default; + } + + return Filter::getInt($this->params->get($param)); + } + /** * @param string $file * @@ -390,12 +337,12 @@ class Request } /** - * @param $param - * @param $default + * @param string $param + * @param bool $default * * @return bool */ - public function analyzeBool($param, $default = null) + public function analyzeBool(string $param, bool $default = null) { if (!$this->params->exists($param)) { return (bool)$default; @@ -410,7 +357,7 @@ class Request * * @throws SPException */ - public function verifySignature($key, $param = null) + public function verifySignature(string $key, string $param = null) { $result = false; @@ -429,4 +376,133 @@ class Request throw new SPException('URI string altered'); } } + + /** + * Returns the URI used by the browser and checks for the protocol used + * + * @see https://tools.ietf.org/html/rfc7239#section-7.5 + * @return string + */ + public function getHttpHost(): string + { + $forwarded = $this->getForwardedData(); + + // Check in style of RFC 7239 + if (null !== $forwarded) { + return strtolower($forwarded['proto'] . '://' . $forwarded['host']); + } + + $xForward = $this->getXForwardedData(); + + // Check (deprecated) de facto standard + if (null !== $xForward) { + return strtolower($xForward['proto'] . '://' . $xForward['host']); + } + + // We got called directly + if ($this->https) { + return 'https://' . $this->request->server()->get('HTTP_HOST'); + } + + return 'http://' . $this->request->server()->get('HTTP_HOST'); + } + + /** + * Devolver datos de forward RFC 7239 + * + * @see https://tools.ietf.org/html/rfc7239#section-7.5 + * @return array|null + */ + public function getForwardedData() + { + $forwarded = $this->getHeader('HTTP_FORWARDED'); + + // Check in style of RFC 7239 + if (!empty($forwarded) + && preg_match('/proto=(\w+);/i', $forwarded, $matchesProto) + && preg_match('/host=(\w+);/i', $forwarded, $matchesHost) + ) { + $data = [ + 'host ' => $matchesHost[0], + 'proto' => $matchesProto[0], + 'for' => $this->getForwardedFor() + ]; + + // Check if protocol and host are not empty + if (!empty($data['proto']) && !empty($data['host'])) { + return $data; + } + } + + return null; + } + + /** + * @param string $header + * + * @return string + */ + public function getHeader(string $header): string + { + return $this->headers->get($header, ''); + } + + /** + * Devolver datos de x-forward + * + * @return array|null + */ + public function getXForwardedData() + { + $forwardedHost = $this->getHeader('HTTP_X_FORWARDED_HOST'); + $forwardedProto = $this->getHeader('HTTP_X_FORWARDED_PROTO'); + + // Check (deprecated) de facto standard + if (!empty($forwardedHost) && !empty($forwardedProto)) { + $data = [ + 'host' => trim(str_replace('"', '', $forwardedHost)), + 'proto' => trim(str_replace('"', '', $forwardedProto)), + 'for' => $this->getForwardedFor() + ]; + + // Check if protocol and host are not empty + if (!empty($data['host']) && !empty($data['proto'])) { + return $data; + } + } + + return null; + } + + /** + * @return string + */ + public function getMethod(): string + { + return $this->method; + } + + /** + * @return bool + */ + public function isHttps(): bool + { + return $this->https; + } + + /** + * @return int + */ + public function getServerPort(): int + { + return (int)$this->request->server()->get('SERVER_PORT', 80); + } + + /** + * @return \Klein\Request + */ + public function getRequest(): \Klein\Request + { + return $this->request; + } } \ No newline at end of file diff --git a/lib/SP/Providers/Mail/MailHandler.php b/lib/SP/Providers/Mail/MailHandler.php index f9e9edf0..a8eb83e8 100644 --- a/lib/SP/Providers/Mail/MailHandler.php +++ b/lib/SP/Providers/Mail/MailHandler.php @@ -27,10 +27,10 @@ namespace SP\Providers\Mail; use SP\Core\Events\Event; use SP\Core\Events\EventReceiver; use SP\Core\Messages\MailMessage; +use SP\Http\Request; use SP\Providers\EventsTrait; use SP\Providers\Provider; use SP\Services\MailService; -use SP\Util\HttpUtil; use SplSubject; /** @@ -63,6 +63,10 @@ class MailHandler extends Provider implements EventReceiver * @var string */ protected $events; + /** + * @var Request + */ + protected $request; /** * Inicialización del observador @@ -88,7 +92,7 @@ class MailHandler extends Provider implements EventReceiver $mailMessage = new MailMessage(); $mailMessage->addDescription($eventMessage->composeText()); $mailMessage->addDescription(sprintf(__('Realizado por: %s (%s)'), $userData->getName(), $userData->getLogin())); - $mailMessage->addDescription(sprintf(__('Dirección IP: %s'), HttpUtil::getClientAddress(true))); + $mailMessage->addDescription(sprintf(__('Dirección IP: %s'), $this->request->getClientAddress(true))); $this->mailService->send($eventMessage->getDescription(), $configData->getMailFrom(), $mailMessage); } catch (\Exception $e) { @@ -121,9 +125,11 @@ class MailHandler extends Provider implements EventReceiver * Receive update from subject * * @link http://php.net/manual/en/splobserver.update.php + * * @param SplSubject $subject

* The SplSubject notifying the observer of an update. *

+ * * @return void * @since 5.1.0 */ @@ -135,6 +141,7 @@ class MailHandler extends Provider implements EventReceiver protected function initialize() { $this->mailService = $this->dic->get(MailService::class); + $this->request = $this->dic->get(Request::class); $configEvents = $this->config->getConfigData()->getMailEvents(); diff --git a/lib/SP/Services/Api/ApiService.php b/lib/SP/Services/Api/ApiService.php index f4606c45..51645a69 100644 --- a/lib/SP/Services/Api/ApiService.php +++ b/lib/SP/Services/Api/ApiService.php @@ -29,6 +29,7 @@ use SP\Core\Acl\ActionsInterface; use SP\Core\Crypt\Hash; use SP\Core\Crypt\Vault; use SP\DataModel\AuthTokenData; +use SP\Http\Request; use SP\Repositories\Track\TrackRequest; use SP\Services\AuthToken\AuthTokenService; use SP\Services\Service; @@ -433,6 +434,6 @@ class ApiService extends Service { $this->authTokenService = $this->dic->get(AuthTokenService::class); $this->trackService = $this->dic->get(TrackService::class); - $this->trackRequest = TrackService::getTrackRequest('api'); + $this->trackRequest = TrackService::getTrackRequest('api', $this->dic->get(Request::class)); } } \ No newline at end of file diff --git a/lib/SP/Services/Auth/LoginService.php b/lib/SP/Services/Auth/LoginService.php index c75160bd..d70d7ea9 100644 --- a/lib/SP/Services/Auth/LoginService.php +++ b/lib/SP/Services/Auth/LoginService.php @@ -413,7 +413,7 @@ class LoginService extends Service $this->request = $this->dic->get(Request::class); $this->userLoginData = new UserLoginData(); - $this->trackRequest = TrackService::getTrackRequest('login'); + $this->trackRequest = TrackService::getTrackRequest('login', $this->request); } /** diff --git a/lib/SP/Services/Crypt/SecureSessionService.php b/lib/SP/Services/Crypt/SecureSessionService.php index 22e3bfc6..1e97da4d 100644 --- a/lib/SP/Services/Crypt/SecureSessionService.php +++ b/lib/SP/Services/Crypt/SecureSessionService.php @@ -32,7 +32,6 @@ use SP\Services\Service; use SP\Services\ServiceException; use SP\Storage\FileCache; use SP\Storage\FileException; -use SP\Util\HttpUtil; /** * Class SecureSessionService @@ -56,22 +55,34 @@ class SecureSessionService extends Service * @var Request */ protected $request; + /** + * @var UUIDCookie + */ + protected $cookie; + /** + * @var string + */ + private $filename; /** * Returns the encryption key * + * @param UUIDCookie $cookie + * * @return Key|false */ - public function getKey() + public function getKey(UUIDCookie $cookie) { + $this->cookie = $cookie; + try { - if ($this->fileCache->isExpired($this->getFileName(), self::CACHE_EXPIRE_TIME)) { + if ($this->fileCache->isExpired($this->getFileNameFromCookie(), self::CACHE_EXPIRE_TIME)) { debugLog('Session key expired or does not exist.'); return $this->saveKey(); } - if (($vault = $this->fileCache->load($this->getFileName())) instanceof Vault) { + if (($vault = $this->fileCache->load($this->getFileNameFromCookie())) instanceof Vault) { return Key::loadFromAsciiSafeString($vault->getData($this->getCypher())); } } catch (FileException $e) { @@ -89,14 +100,19 @@ class SecureSessionService extends Service * @return string * @throws ServiceException */ - private function getFileName() + private function getFileNameFromCookie() { - if (($uuid = UUIDCookie::loadCookie($this->seed)) === false - && ($uuid = UUIDCookie::createCookie($this->seed)) === false) { - throw new ServiceException('Unable to get UUID for filename.'); + if (!$this->filename) { + if (($uuid = $this->cookie->loadCookie($this->seed)) === false + && ($uuid = $this->cookie->createCookie($this->seed)) === false + ) { + throw new ServiceException('Unable to get UUID for filename.'); + } + + $this->filename = self::CACHE_PATH . DIRECTORY_SEPARATOR . $uuid; } - return self::CACHE_PATH . DIRECTORY_SEPARATOR . $uuid; + return $this->filename; } /** @@ -108,7 +124,7 @@ class SecureSessionService extends Service { try { $securedKey = Key::createNewRandomKey(); - $this->fileCache->save($this->getFileName(), (new Vault())->saveData($securedKey->saveToAsciiSafeString(), $this->getCypher())); + $this->fileCache->save($this->getFileNameFromCookie(), (new Vault())->saveData($securedKey->saveToAsciiSafeString(), $this->getCypher())); debugLog('Saved session key.'); @@ -128,13 +144,21 @@ class SecureSessionService extends Service private function getCypher() { return hash_pbkdf2('sha1', - sha1(Request::getRequestHeaders('User-Agent') . HttpUtil::getClientAddress()), + sha1($this->request->getHeader('User-Agent') . $this->request->getClientAddress()), $this->seed, 500, 32 ); } + /** + * @return string + */ + public function getFilename(): string + { + return $this->filename; + } + protected function initialize() { $this->fileCache = $this->dic->get(FileCache::class); diff --git a/lib/SP/Services/EventLog/EventlogService.php b/lib/SP/Services/EventLog/EventlogService.php index 6bfa5a3f..db8a0219 100644 --- a/lib/SP/Services/EventLog/EventlogService.php +++ b/lib/SP/Services/EventLog/EventlogService.php @@ -26,10 +26,10 @@ namespace SP\Services\EventLog; use SP\DataModel\EventlogData; use SP\DataModel\ItemSearchData; +use SP\Http\Request; use SP\Repositories\EventLog\EventlogRepository; use SP\Services\Service; use SP\Storage\Database\QueryResult; -use SP\Util\HttpUtil; /** * Class EventlogService @@ -42,6 +42,10 @@ class EventlogService extends Service * @var EventlogRepository */ protected $eventLogRepository; + /** + * @var Request + */ + protected $request; /** * @param ItemSearchData $itemSearchData @@ -78,7 +82,7 @@ class EventlogService extends Service $eventlogData->setUserId($userData->getId()); $eventlogData->setLogin($userData->getLogin() ?: '-'); - $eventlogData->setIpAddress(HttpUtil::getClientAddress()); + $eventlogData->setIpAddress($this->request->getClientAddress()); return $this->eventLogRepository->create($eventlogData); } @@ -90,5 +94,6 @@ class EventlogService extends Service protected function initialize() { $this->eventLogRepository = $this->dic->get(EventlogRepository::class); + $this->request = $this->dic->get(Request::class); } } \ No newline at end of file diff --git a/lib/SP/Services/PublicLink/PublicLinkService.php b/lib/SP/Services/PublicLink/PublicLinkService.php index 59298050..8fd34369 100644 --- a/lib/SP/Services/PublicLink/PublicLinkService.php +++ b/lib/SP/Services/PublicLink/PublicLinkService.php @@ -40,8 +40,6 @@ use SP\Services\Service; use SP\Services\ServiceException; use SP\Services\ServiceItemTrait; use SP\Storage\Database\QueryResult; -use SP\Util\Checks; -use SP\Util\HttpUtil; use SP\Util\Util; /** @@ -61,6 +59,10 @@ class PublicLinkService extends Service * @var PublicLinkRepository */ protected $publicLinkRepository; + /** + * @var Request + */ + protected $request; /** * Returns an HTTP URL for given hash @@ -294,7 +296,7 @@ class PublicLinkService extends Service { /** @var array $useInfo */ $useInfo = unserialize($publicLinkData->getUseInfo()); - $useInfo[] = self::getUseInfo($publicLinkData->getHash()); + $useInfo[] = self::getUseInfo($publicLinkData->getHash(), $this->request); $publicLinkData->setUseInfo($useInfo); // FIXME @@ -317,18 +319,20 @@ class PublicLinkService extends Service /** * Actualizar la información de uso * - * @param $hash + * @param string $hash + * + * @param Request $request * * @return array */ - public static function getUseInfo($hash) + public static function getUseInfo($hash, Request $request) { return [ - 'who' => HttpUtil::getClientAddress(true), + 'who' => $request->getClientAddress(true), 'time' => time(), 'hash' => $hash, - 'agent' => Request::getRequestHeaders('HTTP_USER_AGENT'), - 'https' => Checks::httpsEnabled() + 'agent' => $request->getHeader('HTTP_USER_AGENT'), + 'https' => $request->isHttps() ]; } @@ -392,5 +396,6 @@ class PublicLinkService extends Service protected function initialize() { $this->publicLinkRepository = $this->dic->get(PublicLinkRepository::class); + $this->request = $this->dic->get(Request::class); } } \ No newline at end of file diff --git a/lib/SP/Services/Track/TrackService.php b/lib/SP/Services/Track/TrackService.php index cb279929..ede4e9dc 100644 --- a/lib/SP/Services/Track/TrackService.php +++ b/lib/SP/Services/Track/TrackService.php @@ -27,10 +27,10 @@ namespace SP\Services\Track; use SP\Core\Events\Event; use SP\Core\Events\EventMessage; use SP\DataModel\TrackData; +use SP\Http\Request; use SP\Repositories\Track\TrackRepository; use SP\Repositories\Track\TrackRequest; use SP\Services\Service; -use SP\Util\HttpUtil; /** * Class TrackService @@ -50,18 +50,24 @@ class TrackService extends Service * @var TrackRepository */ protected $trackRepository; + /** + * @var Request + */ + protected $request; /** - * @param $source + * @param string $source + * + * @param Request $request * * @return TrackRequest * @throws \SP\Core\Exceptions\InvalidArgumentException */ - public static function getTrackRequest($source) + public static function getTrackRequest($source, Request $request) { $trackRequest = new TrackRequest(); $trackRequest->time = time() - self::TIME_TRACKING; - $trackRequest->setTrackIp(HttpUtil::getClientAddress()); + $trackRequest->setTrackIp($request->getClientAddress()); $trackRequest->source = $source; return $trackRequest; @@ -101,15 +107,6 @@ class TrackService extends Service return $this->trackRepository->getAll()->getDataAsArray(); } - /** - * @throws \Psr\Container\ContainerExceptionInterface - * @throws \Psr\Container\NotFoundExceptionInterface - */ - public function initialize() - { - $this->trackRepository = $this->dic->get(TrackRepository::class); - } - /** * Comprobar los intentos de login * @@ -173,7 +170,17 @@ class TrackService extends Service $this->eventDispatcher->notifyEvent('track.add', new Event($this, EventMessage::factory() - ->addDescription(HttpUtil::getClientAddress(true))) + ->addDescription($this->request->getClientAddress(true))) ); } + + /** + * @throws \Psr\Container\ContainerExceptionInterface + * @throws \Psr\Container\NotFoundExceptionInterface + */ + protected function initialize() + { + $this->trackRepository = $this->dic->get(TrackRepository::class); + $this->request = $this->dic->get(Request::class); + } } \ No newline at end of file diff --git a/lib/SP/Util/Checks.php b/lib/SP/Util/Checks.php index 2c00e4ea..959910e3 100644 --- a/lib/SP/Util/Checks.php +++ b/lib/SP/Util/Checks.php @@ -24,8 +24,6 @@ namespace SP\Util; -use Klein\Klein; - /** * Class Checks utilidades de comprobación * @@ -84,7 +82,6 @@ class Checks { $modsNeed = [ 'ldap', - 'mcrypt', 'curl', 'SimpleXML', 'Phar', @@ -140,41 +137,4 @@ class Checks { return extension_loaded('gd'); } - - /** - * Comprobar si se utiliza HTTPS - * - * @return bool - */ - public static function httpsEnabled() - { - return - (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') - || (!empty($_SERVER['SERVER_PORT']) && (int)$_SERVER['SERVER_PORT'] === 443); - } - - /** - * Comprobar si la petición es Ajax - * - * @param Klein $router - * - * @return bool - */ - public static function isAjax(Klein $router) - { - return $router->request()->headers()->get('X-Requested-With') === 'XMLHttpRequest' - || (int)$router->request()->param('isAjax') === 1; - } - - /** - * Comprobar si la petición es en formato JSON - * - * @param Klein $router - * - * @return bool - */ - public static function isJson(Klein $router) - { - return strpos($router->request()->headers()->get('Accept'), 'application/json') !== false; - } } diff --git a/lib/SP/Util/HttpUtil.php b/lib/SP/Util/HttpUtil.php index 33ec01e4..8074f6e5 100644 --- a/lib/SP/Util/HttpUtil.php +++ b/lib/SP/Util/HttpUtil.php @@ -25,6 +25,7 @@ namespace SP\Util; use SP\Config\ConfigData; +use SP\Html\Html; use SP\Http\Request; /** @@ -38,147 +39,78 @@ class HttpUtil * Comprobar y forzar (si es necesario) la conexión HTTPS * * @param ConfigData $configData + * @param Request $request */ - public static function checkHttps(ConfigData $configData) + public static function checkHttps(ConfigData $configData, Request $request) { - if ($configData->isHttpsEnabled() && !Checks::httpsEnabled()) { - $port = ((int)$_SERVER['SERVER_PORT'] !== 443) ? ':' . $_SERVER['SERVER_PORT'] : ''; - $host = str_replace('http', 'https', self::getHttpHost()); + if ($configData->isHttpsEnabled() && !$request->isHttps()) { + $serverPort = $request->getServerPort(); + + $port = $serverPort !== 443 ? ':' . $serverPort : ''; + $host = str_replace('http', 'https', $request->getHttpHost()); header('Location: ' . $host . $port . $_SERVER['REQUEST_URI']); } } /** - * Returns the URI used by the browser and checks for the protocol used + * Devolver las cabeceras enviadas desde el cliente. * - * @see https://tools.ietf.org/html/rfc7239#section-7.5 - * @return string - */ - public static function getHttpHost() - { - $forwarded = self::getForwardedData(); - - // Check in style of RFC 7239 - if (null !== $forwarded) { - return strtolower($forwarded['proto'] . '://' . $forwarded['host']); - } - - $xForward = self::getXForwardedData(); - - // Check (deprecated) de facto standard - if (null !== $xForward) { - return strtolower($xForward['proto'] . '://' . $xForward['host']); - } - - // We got called directly - if (Checks::httpsEnabled()) { - return 'https://' . $_SERVER['HTTP_HOST']; - } - - return 'http://' . $_SERVER['HTTP_HOST']; - } - - /** - * Devolver datos de forward RFC 7239 - * - * @see https://tools.ietf.org/html/rfc7239#section-7.5 - * @return array|null - */ - public static function getForwardedData() - { - $forwarded = Request::getRequestHeaders('HTTP_FORWARDED'); - - // Check in style of RFC 7239 - if ($forwarded !== '' - && preg_match('/proto=(\w+);/i', $forwarded, $matchesProto) - && preg_match('/host=(\w+);/i', $forwarded, $matchesHost) - ) { - $data = [ - 'host ' => $matchesHost[0], - 'proto' => $matchesProto[0], - 'for' => self::getForwardedFor() - ]; - - // Check if protocol and host are not empty - if (!empty($data['proto']) && !empty($data['host'])) { - return $data; - } - } - - return null; - } - - /** - * Devolver la dirección IP del cliente a través de proxy o directo + * @param string $header nombre de la cabecera a devolver * * @return array|string */ - public static function getForwardedFor() + public static function getRequestHeaders($header = '') { - if (preg_match_all('/for="?\[?([\w.:]+)"?\]?[,;]?/i', - Request::getRequestHeaders('HTTP_FORWARDED'), $matchesFor)) { - return $matchesFor[1]; + if (!empty($header)) { + $header = strpos($header, 'HTTP_') === false ? 'HTTP_' . str_replace('-', '_', strtoupper($header)) : $header; + + return isset($_SERVER[$header]) ? $_SERVER[$header] : ''; } - $matchesFor = preg_split('/(?<=[\w])+,/i', - Request::getRequestHeaders('HTTP_X_FORWARDED_FOR'), - -1, - PREG_SPLIT_NO_EMPTY); - - if (count($matchesFor) > 0) { - return $matchesFor; - } - - return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : ''; + return self::getApacheHeaders(); } /** - * Devolver datos de x-forward + * Función que sustituye a apache_request_headers * - * @return array|null + * @return array */ - public static function getXForwardedData() + private static function getApacheHeaders() { - $forwardedHost = Request::getRequestHeaders('HTTP_X_FORWARDED_HOST'); - $forwardedProto = Request::getRequestHeaders('HTTP_X_FORWARDED_PROTO'); + if (function_exists('\apache_request_headers')) { + return apache_request_headers(); + } - // Check (deprecated) de facto standard - if (!empty($forwardedHost) && !empty($forwardedProto)) { - $data = [ - 'host' => trim(str_replace('"', '', $forwardedHost)), - 'proto' => trim(str_replace('"', '', $forwardedProto)), - 'for' => self::getForwardedFor() - ]; + $headers = []; - // Check if protocol and host are not empty - if (!empty($data['host']) && !empty($data['proto'])) { - return $data; + foreach ($_SERVER as $key => $value) { + if (strpos($key, 'HTTP_') === 0) { + $key = ucwords(strtolower(str_replace('_', '-', substr($key, 5))), '-'); + $headers[$key] = $value; + } else { + $headers[$key] = $value; } } - return null; + return $headers; } /** - * Devolver la dirección IP del cliente - * - * @param bool $fullForwarded Devolver la cadena de forward completa - * - * @return string|array + * Comprobar si existen parámetros pasados por POST para enviarlos por GET */ - public static function getClientAddress($fullForwarded = false) + public static function importUrlParamsToGet() { - if (APP_MODULE === 'tests') { - return '127.0.0.1'; + $params = []; + + foreach ($_REQUEST as $param => $value) { + $param = Filter::getString($param); + + if (strpos($param, 'g_') !== false) { + $params[] = substr($param, 2) . '=' . Html::sanitize($value); + } } - $forwarded = self::getForwardedFor(); - - if (is_array($forwarded)) { - return $fullForwarded ? implode(',', $forwarded) : $forwarded[0]; - } - - return $forwarded; + return count($params) > 0 ? '?' . implode('&', $params) : ''; } } \ No newline at end of file diff --git a/lib/SP/Util/Util.php b/lib/SP/Util/Util.php index 7dd33a57..4a0369cf 100644 --- a/lib/SP/Util/Util.php +++ b/lib/SP/Util/Util.php @@ -481,25 +481,12 @@ class Util */ public static function arrayJSEscape(&$array) { - array_walk($array, function (&$value, $index) { + array_walk($array, function (&$value) { $value = str_replace(['\'', '"'], '\\\'', $value); }); return $array; } - /** - * Obtener la URL de acceso al servidor - * - * @return string - */ - public static function getServerUrl() - { - $urlScheme = Checks::httpsEnabled() ? 'https://' : 'http://'; - $urlPort = ((int)$_SERVER['SERVER_PORT'] !== 443) ? ':' . $_SERVER['SERVER_PORT'] : ''; - - return $urlScheme . $_SERVER['SERVER_NAME'] . $urlPort; - } - /** * Cast an object to another class, keeping the properties, but changing the methods * diff --git a/tests/Repositories/PublicLinkRepositoryTest.php b/tests/Repositories/PublicLinkRepositoryTest.php index 92012957..ddb8b7a4 100644 --- a/tests/Repositories/PublicLinkRepositoryTest.php +++ b/tests/Repositories/PublicLinkRepositoryTest.php @@ -31,7 +31,6 @@ use SP\DataModel\PublicLinkData; use SP\DataModel\PublicLinkListData; use SP\Repositories\DuplicatedItemException; use SP\Repositories\PublicLink\PublicLinkRepository; -use SP\Services\PublicLink\PublicLinkService; use SP\Storage\Database\DatabaseConnectionData; use SP\Tests\DatabaseTestCase; use SP\Util\Util; @@ -253,9 +252,17 @@ class PublicLinkRepositoryTest extends DatabaseTestCase { $hash = pack('H*', '313065363937306666653833623531393234356635333433333732626366663433376461623565356134386238326131653238636131356235346635'); + $useInfo = [ + 'who' => '127.0.0.1', + 'time' => time(), + 'hash' => $hash, + 'agent' => 'Mozilla/Firefox', + 'https' => true + ]; + $data = new PublicLinkData(); $data->setHash($hash); - $data->setUseInfo(PublicLinkService::getUseInfo($hash)); + $data->setUseInfo($useInfo); $this->assertEquals(1, self::$repository->addLinkView($data)); diff --git a/tests/Services/Crypt/SecureSessionServiceTest.php b/tests/Services/Crypt/SecureSessionServiceTest.php new file mode 100644 index 00000000..7644cbbb --- /dev/null +++ b/tests/Services/Crypt/SecureSessionServiceTest.php @@ -0,0 +1,59 @@ +. + */ + +namespace SP\Tests\Services\Crypt; + +use Defuse\Crypto\Key; +use PHPUnit\Framework\TestCase; +use SP\Core\Crypt\UUIDCookie; +use SP\Services\Crypt\SecureSessionService; +use function SP\Tests\setupContext; + +/** + * Class SecureSessionServiceTest + * + * @package SP\Tests\Services\Crypt + */ +class SecureSessionServiceTest extends TestCase +{ + /** + * @throws \DI\DependencyException + * @throws \DI\NotFoundException + * @throws \SP\Core\Context\ContextException + */ + public function testGetKey() + { + $dic = setupContext(); + $service = $dic->get(SecureSessionService::class); + + $stub = $this->createMock(UUIDCookie::class); + $stub->method('loadCookie') + ->willReturn(uniqid('', true)); + $stub->method('createCookie') + ->willReturn(uniqid('', true)); + + $this->assertInstanceOf(Key::class, $service->getKey($stub)); + $this->assertFileExists($service->getFilename()); + } +}