diff --git a/app/modules/web/Controllers/AccountController.php b/app/modules/web/Controllers/AccountController.php index 471fd42f..11a2fe3b 100644 --- a/app/modules/web/Controllers/AccountController.php +++ b/app/modules/web/Controllers/AccountController.php @@ -209,7 +209,8 @@ final class AccountController extends ControllerBase implements CrudControllerIn $this->view->assign('useImage', $this->configData->isPublinksImageEnabled() || $this->configData->isAccountPassToImage()); if ($this->view->useImage) { - $this->view->assign('accountPassImage', ImageUtil::convertText($accountData->getPass())); + $imageUtil = $this->dic->get(ImageUtil::class); + $this->view->assign('accountPassImage', $imageUtil->convertText($accountData->getPass())); } else { $this->view->assign('copyPassRoute', Acl::getActionRoute(Acl::ACCOUNT_VIEW_PASS)); } diff --git a/app/modules/web/Controllers/BootstrapController.php b/app/modules/web/Controllers/BootstrapController.php index 35fd8752..a708fe84 100644 --- a/app/modules/web/Controllers/BootstrapController.php +++ b/app/modules/web/Controllers/BootstrapController.php @@ -24,12 +24,12 @@ namespace SP\Modules\Web\Controllers; -use phpseclib\Crypt\RSA; use SP\Bootstrap; use SP\Core\Crypt\CryptPKI; -use SP\Http\Cookies; -use SP\Http\Response; +use SP\Modules\Web\Controllers\Traits\JsonTrait; +use SP\Plugin\PluginManager; use SP\Providers\Auth\Browser\Browser; +use SP\Services\Import\ImportService; /** * Class BootstrapController @@ -38,6 +38,8 @@ use SP\Providers\Auth\Browser\Browser; */ final class BootstrapController extends SimpleControllerBase { + use JsonTrait; + /** * Returns environment data * @@ -46,29 +48,70 @@ final class BootstrapController extends SimpleControllerBase */ public function getEnvironmentAction() { - $configData = $this->config->getConfigData(); - - $checkStatus = $this->session->getAuthCompleted() && ($this->session->getUserData()->getIsAdminApp() || $configData->isDemoEnabled()); + $checkStatus = $this->session->getAuthCompleted() && ($this->session->getUserData()->getIsAdminApp() || $this->configData->isDemoEnabled()); $data = [ - 'lang' => require CONFIG_PATH . DIRECTORY_SEPARATOR . 'strings.js.inc', - 'locale' => $configData->getSiteLang(), + 'lang' => $this->getJsLang(), + 'locale' => $this->configData->getSiteLang(), 'app_root' => Bootstrap::$WEBURI, - 'max_file_size' => $configData->getFilesAllowedSize(), - 'check_updates' => $checkStatus && $configData->isCheckUpdates(), - 'check_notices' => $checkStatus && $configData->isChecknotices(), + 'max_file_size' => $this->configData->getFilesAllowedSize(), + 'check_updates' => $checkStatus && $this->configData->isCheckUpdates(), + 'check_notices' => $checkStatus && $this->configData->isChecknotices(), 'timezone' => date_default_timezone_get(), - 'debug' => DEBUG || $configData->isDebug(), - 'cookies_enabled' => Cookies::checkCookies(), -// 'plugins' => PluginUtil::getEnabledPlugins(), - 'plugins' => [], + 'debug' => DEBUG || $this->configData->isDebug(), + 'cookies_enabled' => $this->getCookiesEnabled(), + 'plugins' => $this->getPlugins(), 'loggedin' => $this->session->isLoggedIn(), - 'authbasic_autologin' => Browser::getServerAuthUser() && $configData->isAuthBasicAutoLoginEnabled(), - 'pk' => $this->session->getPublicKey() ?: (new CryptPKI($this->dic->get(RSA::class)))->getPublicKey(), - 'import_allowed_exts' => ['CSV', 'XML'], - 'files_allowed_exts' => $configData->getFilesAllowedExts() + 'authbasic_autologin' => $this->getAuthBasicAutologinEnabled(), + 'pk' => $this->getPublicKey(), + 'import_allowed_exts' => ImportService::ALLOWED_EXTS, + 'files_allowed_exts' => $this->configData->getFilesAllowedExts() ]; - Response::printJson($data, 0); + $this->returnJsonResponseData($data); + } + + /** + * @return array + */ + private function getJsLang() + { + return require CONFIG_PATH . DIRECTORY_SEPARATOR . 'strings.js.inc'; + } + + /** + * @return bool + */ + private function getCookiesEnabled() + { + return $this->router->request()->cookies()->get(session_name()) !== null; + } + + /** + * @return array + * @throws \SP\Core\Exceptions\ConstraintException + * @throws \SP\Core\Exceptions\QueryException + */ + private function getPlugins() + { + return $this->dic->get(PluginManager::class)->getEnabledPlugins(); + } + + /** + * @return bool + */ + private function getAuthBasicAutologinEnabled() + { + return Browser::getServerAuthUser() && $this->configData->isAuthBasicAutoLoginEnabled(); + } + + /** + * @return string + * @throws \SP\Core\Exceptions\FileNotFoundException + * @throws \SP\Core\Exceptions\SPException + */ + private function getPublicKey() + { + return $this->session->getPublicKey() ?: $this->dic->get(CryptPKI::class)->getPublicKey(); } } \ No newline at end of file diff --git a/app/modules/web/Controllers/ControllerBase.php b/app/modules/web/Controllers/ControllerBase.php index 1accb068..1f57ba6a 100644 --- a/app/modules/web/Controllers/ControllerBase.php +++ b/app/modules/web/Controllers/ControllerBase.php @@ -27,21 +27,11 @@ namespace SP\Modules\Web\Controllers; defined('APP_ROOT') || die(); use DI\Container; -use Klein\Klein; use Psr\Container\ContainerInterface; -use SP\Config\Config; -use SP\Config\ConfigData; -use SP\Core\Acl\Acl; -use SP\Core\Context\ContextInterface; -use SP\Core\Context\SessionContext; -use SP\Core\Events\EventDispatcher; use SP\Core\Exceptions\FileNotFoundException; -use SP\Core\PhpExtensionChecker; -use SP\Core\UI\Theme; use SP\DataModel\ProfileData; -use SP\Http\Request; use SP\Modules\Web\Controllers\Helpers\LayoutHelper; -use SP\Mvc\Controller\ControllerTrait; +use SP\Modules\Web\Controllers\Traits\WebControllerTrait; use SP\Mvc\View\Template; use SP\Providers\Auth\Browser\Browser; use SP\Services\Auth\AuthException; @@ -52,7 +42,7 @@ use SP\Services\User\UserLoginResponse; */ abstract class ControllerBase { - use ControllerTrait; + use WebControllerTrait; /** * Constantes de errores @@ -68,18 +58,6 @@ abstract class ControllerBase * @var Template Instancia del motor de plantillas a utilizar */ protected $view; - /** - * @var int ID de la acción - */ - protected $action; - /** - * @var string Nombre de la acción - */ - protected $actionName; - /** - * @var string Nombre del controlador - */ - protected $controllerName; /** * @var UserLoginResponse */ @@ -88,34 +66,6 @@ abstract class ControllerBase * @var ProfileData */ protected $userProfileData; - /** - * @var EventDispatcher - */ - protected $eventDispatcher; - /** - * @var ConfigData - */ - protected $configData; - /** - * @var Config - */ - protected $config; - /** - * @var SessionContext - */ - protected $session; - /** - * @var Theme - */ - protected $theme; - /** - * @var \SP\Core\Acl\Acl - */ - protected $acl; - /** - * @var Klein - */ - protected $router; /** * @var ContainerInterface */ @@ -124,14 +74,6 @@ abstract class ControllerBase * @var */ protected $isAjax = false; - /** - * @var Request - */ - protected $request; - /** - * @var PhpExtensionChecker - */ - protected $extensionChecker; /** * Constructor @@ -145,20 +87,11 @@ abstract class ControllerBase public final function __construct(Container $container, $actionName) { $this->dic = $container; - - $this->controllerName = $this->getControllerName(); $this->actionName = $actionName; - $this->config = $this->dic->get(Config::class); - $this->configData = $this->config->getConfigData(); - $this->session = $this->dic->get(ContextInterface::class); - $this->theme = $this->dic->get(Theme::class); - $this->eventDispatcher = $this->dic->get(EventDispatcher::class); - $this->acl = $this->dic->get(Acl::class); - $this->router = $this->dic->get(Klein::class); + $this->setUp($container); + $this->view = $this->dic->get(Template::class); - $this->request = $this->dic->get(Request::class); - $this->extensionChecker = $this->dic->get(PhpExtensionChecker::class); $this->view->setBase(strtolower($this->controllerName)); @@ -202,14 +135,16 @@ abstract class ControllerBase protected function view() { try { - echo $this->view->render(); + $this->router->response() + ->body($this->view->render()) + ->send(); } catch (FileNotFoundException $e) { processException($e); - echo __($e->getMessage()); + $this->router->response() + ->body(__($e->getMessage())) + ->send(true); } - - die(); } /** diff --git a/app/modules/web/Controllers/Helpers/Account/AccountPasswordHelper.php b/app/modules/web/Controllers/Helpers/Account/AccountPasswordHelper.php index 560772e5..82ecae97 100644 --- a/app/modules/web/Controllers/Helpers/Account/AccountPasswordHelper.php +++ b/app/modules/web/Controllers/Helpers/Account/AccountPasswordHelper.php @@ -116,7 +116,9 @@ final class AccountPasswordHelper extends HelperBase $pass = $this->getPasswordClear($accountData); if ($this->configData->isAccountPassToImage()) { - $this->view->assign('pass', ImageUtil::convertText($pass)); + $imageUtil = $this->dic->get(ImageUtil::class); + + $this->view->assign('pass', $imageUtil->convertText($pass)); $this->view->assign('isImage', 1); } else { $this->view->assign('pass', htmlentities($pass)); diff --git a/app/modules/web/Controllers/Helpers/LayoutHelper.php b/app/modules/web/Controllers/Helpers/LayoutHelper.php index 360fc804..bed4e267 100644 --- a/app/modules/web/Controllers/Helpers/LayoutHelper.php +++ b/app/modules/web/Controllers/Helpers/LayoutHelper.php @@ -37,6 +37,7 @@ use SP\Http\Uri; use SP\Plugin\PluginManager; use SP\Services\Install\Installer; use SP\Util\Util; +use SP\Util\Version; /** * Class LayoutHelper @@ -134,7 +135,7 @@ final class LayoutHelper extends HelperBase */ protected function getResourcesLinks() { - $version = Util::getVersionStringNormalized(); + $version = Version::getVersionStringNormalized(); $jsUri = new Uri(Bootstrap::$WEBURI . '/index.php'); $jsUri->addParam('_r', 'resource/js'); diff --git a/app/modules/web/Controllers/InstallController.php b/app/modules/web/Controllers/InstallController.php index 45089283..285c40a0 100644 --- a/app/modules/web/Controllers/InstallController.php +++ b/app/modules/web/Controllers/InstallController.php @@ -28,13 +28,13 @@ use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; use SP\Core\Exceptions\SPException; use SP\Core\Language; +use SP\Core\PhpExtensionChecker; use SP\Http\JsonResponse; use SP\Modules\Web\Controllers\Helpers\LayoutHelper; use SP\Modules\Web\Controllers\Traits\JsonTrait; use SP\Mvc\View\Components\SelectItemAdapter; use SP\Services\Install\InstallData; use SP\Services\Install\Installer; -use SP\Util\Checks; /** * Class InstallController @@ -56,40 +56,14 @@ final class InstallController extends ControllerBase $errors = []; - if (!Checks::checkPhpVersion()) { - $errors[] = [ - 'type' => SPException::CRITICAL, - 'description' => __('Versión de PHP requerida >= ') . ' 5.6.0 <= 7.0', - 'hint' => __('Actualice la versión de PHP para que la aplicación funcione correctamente') + foreach ($this->dic->get(PhpExtensionChecker::class)->getMissing() as $module) { + $error[] = [ + 'type' => SPException::WARNING, + 'description' => sprintf('%s (%s)', __('Módulo no disponible'), $module), + 'hint' => __('Sin este módulo la aplicación puede no funcionar correctamente.') ]; } - $modules = Checks::checkModules(); - - if (count($modules) > 0) { - foreach ($modules as $module) { - $error[] = [ - 'type' => SPException::WARNING, - 'description' => sprintf('%s (%s)', __('Módulo no disponible'), $module), - 'hint' => __('Sin este módulo la aplicación puede no funcionar correctamente.') - ]; - } - } - - if (@file_exists(__FILE__ . "\0Nullbyte")) { - $errors[] = [ - 'type' => SPException::WARNING, - 'description' => __('La version de PHP es vulnerable al ataque NULL Byte (CVE-2006-7243)'), - 'hint' => __('Actualice la versión de PHP para usar sysPass de forma segura')]; - } - - if (!Checks::secureRNGIsAvailable()) { - $errors[] = [ - 'type' => SPException::WARNING, - 'description' => __('No se encuentra el generador de números aleatorios.'), - 'hint' => __('Sin esta función un atacante puede utilizar su cuenta al resetear la clave')]; - } - $this->view->assign('errors', $errors); $this->view->assign('langs', SelectItemAdapter::factory(Language::getAvailableLanguages())->getItemsFromArraySelected([Language::$globalLang])); diff --git a/app/modules/web/Controllers/ItemsController.php b/app/modules/web/Controllers/ItemsController.php index ffe9dc6d..b975315f 100644 --- a/app/modules/web/Controllers/ItemsController.php +++ b/app/modules/web/Controllers/ItemsController.php @@ -25,13 +25,13 @@ namespace SP\Modules\Web\Controllers; use SP\DataModel\DataModelInterface; +use SP\Http\Json; use SP\Http\JsonResponse; use SP\Mvc\View\Components\SelectItemAdapter; use SP\Services\Account\AccountService; use SP\Services\Category\CategoryService; use SP\Services\Client\ClientService; use SP\Services\Notification\NotificationService; -use SP\Util\Json; /** * Class ItemsController @@ -65,7 +65,7 @@ final class ItemsController extends SimpleControllerBase $jsonResponse->setData($outItems); $jsonResponse->setCsrf($this->session->getSecurityKey()); - Json::returnJson($jsonResponse); + Json::fromDic()->returnJson($jsonResponse); } /** @@ -73,7 +73,8 @@ final class ItemsController extends SimpleControllerBase */ public function clientsAction() { - Json::returnRawJson(SelectItemAdapter::factory($this->dic->get(ClientService::class)->getAllForUser())->getJsonItemsFromModel()); + Json::factory($this->router->response()) + ->returnRawJson(SelectItemAdapter::factory($this->dic->get(ClientService::class)->getAllForUser())->getJsonItemsFromModel()); } /** @@ -81,7 +82,8 @@ final class ItemsController extends SimpleControllerBase */ public function categoriesAction() { - Json::returnRawJson(SelectItemAdapter::factory($this->dic->get(CategoryService::class)->getAllBasic())->getJsonItemsFromModel()); + Json::factory($this->router->response()) + ->returnRawJson(SelectItemAdapter::factory($this->dic->get(CategoryService::class)->getAllBasic())->getJsonItemsFromModel()); } /** @@ -89,7 +91,8 @@ final class ItemsController extends SimpleControllerBase */ public function notificationsAction() { - Json::returnRawJson(Json::getJson($this->dic->get(NotificationService::class)->getAllActiveForUserId($this->session->getUserData()->getId()))); + Json::factory($this->router->response()) + ->returnRawJson(Json::getJson($this->dic->get(NotificationService::class)->getAllActiveForUserId($this->session->getUserData()->getId()))); } /** diff --git a/app/modules/web/Controllers/NotificationController.php b/app/modules/web/Controllers/NotificationController.php index 503a4bc0..93b7a811 100644 --- a/app/modules/web/Controllers/NotificationController.php +++ b/app/modules/web/Controllers/NotificationController.php @@ -83,6 +83,8 @@ final class NotificationController extends ControllerBase implements CrudControl protected function getSearchGrid() { $itemsGridHelper = $this->dic->get(ItemsGridHelper::class); + $itemsGridHelper->setQueryTimeStart(microtime(true)); + $itemSearchData = $this->getSearchData($this->configData->getAccountCount(), $this->request); return $itemsGridHelper->updatePager($itemsGridHelper->getNotificationsGrid($this->notificationService->search($itemSearchData)), $itemSearchData); @@ -125,6 +127,7 @@ final class NotificationController extends ControllerBase implements CrudControl * * @throws \SP\Core\Exceptions\ConstraintException * @throws \SP\Core\Exceptions\QueryException + * @throws \SP\Repositories\NoSuchItemException */ protected function setViewData($notificationId = null) { @@ -151,7 +154,8 @@ final class NotificationController extends ControllerBase implements CrudControl } /** - * Search action + * @throws \SP\Core\Exceptions\ConstraintException + * @throws \SP\Core\Exceptions\QueryException */ public function searchAction() { diff --git a/app/modules/web/Controllers/PluginController.php b/app/modules/web/Controllers/PluginController.php index 6329b355..af716e03 100644 --- a/app/modules/web/Controllers/PluginController.php +++ b/app/modules/web/Controllers/PluginController.php @@ -140,6 +140,7 @@ final class PluginController extends ControllerBase * * @throws \SP\Core\Exceptions\ConstraintException * @throws \SP\Core\Exceptions\QueryException + * @throws \SP\Repositories\NoSuchItemException */ protected function setViewData($pluginId = null) { diff --git a/app/modules/web/Controllers/ResourceController.php b/app/modules/web/Controllers/ResourceController.php index e3d3fa93..67b8f916 100644 --- a/app/modules/web/Controllers/ResourceController.php +++ b/app/modules/web/Controllers/ResourceController.php @@ -40,26 +40,21 @@ final class ResourceController extends SimpleControllerBase protected $minify; /** - * @throws \Psr\Container\ContainerExceptionInterface - * @throws \Psr\Container\NotFoundExceptionInterface - * @throws SPException + * Returns CSS resources */ public function cssAction() { - $this->request->verifySignature($this->configData->getPasswordSalt()); - $file = $this->request->analyzeString('f'); $base = $this->request->analyzeString('b'); - $minify = $this->dic->get(Minify::class); - if ($file && $base) { - $minify->setType(Minify::FILETYPE_CSS) + $this->minify + ->setType(Minify::FILETYPE_CSS) ->setBase(urldecode($base), true) ->addFilesFromString(urldecode($file)) ->getMinified(); } else { - $minify->setType(Minify::FILETYPE_CSS) + $this->minify->setType(Minify::FILETYPE_CSS) ->setBase(PUBLIC_PATH . DIRECTORY_SEPARATOR . 'css') ->addFiles(['reset.min.css', 'jquery-ui.min.css', @@ -74,32 +69,27 @@ final class ResourceController extends SimpleControllerBase } /** - * @throws \Psr\Container\ContainerExceptionInterface - * @throws \Psr\Container\NotFoundExceptionInterface - * @throws SPException + * Returns JS resources */ public function jsAction() { - $this->request->verifySignature($this->configData->getPasswordSalt()); - $file = $this->request->analyzeString('f'); $base = $this->request->analyzeString('b'); - $minify = $this->dic->get(Minify::class); - if ($file && $base) { - $minify->setType(Minify::FILETYPE_JS) + $this->minify + ->setType(Minify::FILETYPE_JS) ->setBase(urldecode($base), true) ->addFilesFromString(urldecode($file)) ->getMinified(); } else { - $minify->setType(Minify::FILETYPE_JS) + $this->minify->setType(Minify::FILETYPE_JS) ->setBase(PUBLIC_PATH . DIRECTORY_SEPARATOR . 'js'); $group = $this->request->analyzeInt('g', 0); if ($group === 0) { - $minify->addFiles([ + $this->minify->addFiles([ 'jquery-3.3.1.min.js', 'jquery-migrate-3.0.0.min.js', 'jquery.fileDownload.min.js', @@ -116,7 +106,7 @@ final class ResourceController extends SimpleControllerBase 'eventsource.min.js'], false); } elseif ($group === 1) { // FIXME: use MIN version - $minify->addFiles([ + $this->minify->addFiles([ 'app.js', 'app-triggers.js', 'app-actions.js', @@ -124,7 +114,17 @@ final class ResourceController extends SimpleControllerBase 'app-main.js'], false); } - $minify->getMinified(); + $this->minify->getMinified(); } } + + /** + * @throws SPException + */ + protected function initialize() + { + $this->request->verifySignature($this->configData->getPasswordSalt()); + + $this->minify = $this->dic->get(Minify::class); + } } \ No newline at end of file diff --git a/app/modules/web/Controllers/SimpleControllerBase.php b/app/modules/web/Controllers/SimpleControllerBase.php index 18839aa8..d714dae8 100644 --- a/app/modules/web/Controllers/SimpleControllerBase.php +++ b/app/modules/web/Controllers/SimpleControllerBase.php @@ -25,19 +25,9 @@ namespace SP\Modules\Web\Controllers; use DI\Container; -use Klein\Klein; use Psr\Container\ContainerInterface; -use SP\Config\Config; -use SP\Config\ConfigData; -use SP\Core\Acl\Acl; use SP\Core\Acl\UnauthorizedPageException; -use SP\Core\Context\ContextInterface; -use SP\Core\Context\SessionContext; -use SP\Core\Events\EventDispatcher; -use SP\Core\PhpExtensionChecker; -use SP\Core\UI\Theme; -use SP\Http\Request; -use SP\Mvc\Controller\ControllerTrait; +use SP\Modules\Web\Controllers\Traits\WebControllerTrait; /** * Class SimpleControllerBase @@ -46,56 +36,12 @@ use SP\Mvc\Controller\ControllerTrait; */ abstract class SimpleControllerBase { - use ControllerTrait; + use WebControllerTrait; - /** - * @var string Nombre del controlador - */ - protected $controllerName; - /** - * @var EventDispatcher - */ - protected $eventDispatcher; - /** - * @var Config - */ - protected $config; - /** - * @var SessionContext - */ - protected $session; - /** - * @var Theme - */ - protected $theme; - /** - * @var string - */ - protected $actionName; - /** - * @var Klein - */ - protected $router; /** * @var ContainerInterface */ protected $dic; - /** - * @var Acl - */ - protected $acl; - /** - * @var ConfigData - */ - protected $configData; - /** - * @var Request - */ - protected $request; - /** - * @var PhpExtensionChecker - */ - protected $extensionChecker; /** * SimpleControllerBase constructor. @@ -109,19 +55,9 @@ abstract class SimpleControllerBase public function __construct(Container $container, $actionName) { $this->dic = $container; - - $this->controllerName = $this->getControllerName(); $this->actionName = $actionName; - $this->config = $this->dic->get(Config::class); - $this->configData = $this->config->getConfigData(); - $this->session = $this->dic->get(ContextInterface::class); - $this->theme = $this->dic->get(Theme::class); - $this->eventDispatcher = $this->dic->get(EventDispatcher::class); - $this->router = $this->dic->get(Klein::class); - $this->request = $this->dic->get(Request::class); - $this->acl = $this->dic->get(Acl::class); - $this->extensionChecker = $this->dic->get(PhpExtensionChecker::class); + $this->setUp($container); if (method_exists($this, 'initialize')) { $this->initialize(); diff --git a/app/modules/web/Controllers/StatusController.php b/app/modules/web/Controllers/StatusController.php index 1f7baaf4..e3ecf9be 100644 --- a/app/modules/web/Controllers/StatusController.php +++ b/app/modules/web/Controllers/StatusController.php @@ -30,6 +30,7 @@ use SP\Core\Exceptions\CheckException; use SP\Http\JsonResponse; use SP\Modules\Web\Controllers\Traits\JsonTrait; use SP\Util\Util; +use SP\Util\Version; /** * Class StatusController @@ -70,7 +71,7 @@ final class StatusController extends SimpleControllerBase if (preg_match('/v?(?P\d+)\.(?P\d+)\.(?P\d+)\.(?P\d+)(?P\-[a-z0-9\.]+)?$/', $requestData->tag_name, $matches)) { $pubVersion = $matches['major'] . $matches['minor'] . $matches['patch'] . '.' . $matches['build']; - if (Util::checkVersion(Util::getVersionStringNormalized(), $pubVersion)) { + if (Version::checkVersion(Version::getVersionStringNormalized(), $pubVersion)) { $this->returnJsonResponseData([ 'version' => $requestData->tag_name, 'url' => $requestData->html_url, diff --git a/app/modules/web/Controllers/Traits/JsonTrait.php b/app/modules/web/Controllers/Traits/JsonTrait.php index f317185f..7446889b 100644 --- a/app/modules/web/Controllers/Traits/JsonTrait.php +++ b/app/modules/web/Controllers/Traits/JsonTrait.php @@ -25,8 +25,8 @@ namespace SP\Modules\Web\Controllers\Traits; use SP\Core\Exceptions\SPException; +use SP\Http\Json; use SP\Http\JsonResponse; -use SP\Util\Json; /** * Trait JsonTrait @@ -52,7 +52,7 @@ trait JsonTrait $jsonResponse->setMessages($messages); } - Json::returnJson($jsonResponse); + Json::fromDic()->returnJson($jsonResponse); } /** @@ -77,7 +77,7 @@ trait JsonTrait $jsonResponse->setMessages($messages); } - Json::returnJson($jsonResponse); + Json::fromDic()->returnJson($jsonResponse); } /** @@ -96,6 +96,6 @@ trait JsonTrait $jsonResponse->setMessages([$exception->getHint()]); } - Json::returnJson($jsonResponse); + Json::fromDic()->returnJson($jsonResponse); } } \ No newline at end of file diff --git a/app/modules/web/Controllers/Traits/WebControllerTrait.php b/app/modules/web/Controllers/Traits/WebControllerTrait.php new file mode 100644 index 00000000..dc31da62 --- /dev/null +++ b/app/modules/web/Controllers/Traits/WebControllerTrait.php @@ -0,0 +1,111 @@ +. + */ + +namespace SP\Modules\Web\Controllers\Traits; + +use Klein\Klein; +use Psr\Container\ContainerInterface; +use SP\Config\Config; +use SP\Config\ConfigData; +use SP\Core\Acl\Acl; +use SP\Core\Context\ContextInterface; +use SP\Core\Context\SessionContext; +use SP\Core\Events\EventDispatcher; +use SP\Core\PhpExtensionChecker; +use SP\Core\UI\Theme; +use SP\Http\Request; +use SP\Mvc\Controller\ControllerTrait; + +/** + * Trait ControllerTratit + * + * @package SP\Modules\Web\Controllers + */ +trait WebControllerTrait +{ + use ControllerTrait; + + /** + * @var string Nombre del controlador + */ + protected $controllerName; + /** + * @var EventDispatcher + */ + protected $eventDispatcher; + /** + * @var Config + */ + protected $config; + /** + * @var SessionContext + */ + protected $session; + /** + * @var Theme + */ + protected $theme; + /** + * @var string + */ + protected $actionName; + /** + * @var Klein + */ + protected $router; + /** + * @var Acl + */ + protected $acl; + /** + * @var ConfigData + */ + protected $configData; + /** + * @var Request + */ + protected $request; + /** + * @var PhpExtensionChecker + */ + protected $extensionChecker; + + /** + * @param ContainerInterface $dic + */ + private function setUp(ContainerInterface $dic) + { + $this->controllerName = $this->getControllerName(); + + $this->config = $dic->get(Config::class); + $this->configData = $this->config->getConfigData(); + $this->session = $dic->get(ContextInterface::class); + $this->theme = $dic->get(Theme::class); + $this->eventDispatcher = $dic->get(EventDispatcher::class); + $this->router = $dic->get(Klein::class); + $this->request = $dic->get(Request::class); + $this->acl = $dic->get(Acl::class); + $this->extensionChecker = $dic->get(PhpExtensionChecker::class); + } +} \ No newline at end of file diff --git a/app/modules/web/Controllers/UserController.php b/app/modules/web/Controllers/UserController.php index 6791c968..4080e8db 100644 --- a/app/modules/web/Controllers/UserController.php +++ b/app/modules/web/Controllers/UserController.php @@ -215,7 +215,7 @@ final class UserController extends ControllerBase implements CrudControllerInter public function editPassAction($id) { // Comprobar si el usuario a modificar es distinto al de la sesión - if (!$this->acl->checkUserAccess(Acl::USER_EDIT_PASS, $this->userData->getId())) { + if (!$this->acl->checkUserAccess(Acl::USER_EDIT_PASS, $id)) { return; } @@ -253,7 +253,7 @@ final class UserController extends ControllerBase implements CrudControllerInter public function deleteAction($id = null) { if (!$this->acl->checkUserAccess(Acl::USER_DELETE)) { - return; + $this->returnJsonResponse(JsonResponse::JSON_ERROR, __u('No tiene permisos para realizar esta operación')); } $this->view->assign(__FUNCTION__, 1); @@ -295,7 +295,7 @@ final class UserController extends ControllerBase implements CrudControllerInter public function saveCreateAction() { if (!$this->acl->checkUserAccess(Acl::USER_CREATE)) { - return; + $this->returnJsonResponse(JsonResponse::JSON_ERROR, __u('No tiene permisos para realizar esta operación')); } try { @@ -356,7 +356,7 @@ final class UserController extends ControllerBase implements CrudControllerInter public function saveEditAction($id) { if (!$this->acl->checkUserAccess(Acl::USER_EDIT)) { - return; + $this->returnJsonResponse(JsonResponse::JSON_ERROR, __u('No tiene permisos para realizar esta operación')); } try { @@ -394,8 +394,8 @@ final class UserController extends ControllerBase implements CrudControllerInter */ public function saveEditPassAction($id) { - if (!$this->acl->checkUserAccess(Acl::USER_EDIT_PASS)) { - return; + if (!$this->acl->checkUserAccess(Acl::USER_EDIT_PASS, $id)) { + $this->returnJsonResponse(JsonResponse::JSON_ERROR, __u('No tiene permisos para realizar esta operación')); } try { diff --git a/app/modules/web/Forms/UserForm.php b/app/modules/web/Forms/UserForm.php index 859fa478..ce0d0935 100644 --- a/app/modules/web/Forms/UserForm.php +++ b/app/modules/web/Forms/UserForm.php @@ -138,8 +138,8 @@ final class UserForm extends FormBase implements FormInterface private function isDemo() { return $this->configData->isDemoEnabled() - && ($this->userData->getLogin() === 'demo' - && $this->userData->isAdminApp() === 0); + && $this->itemId === 2 // FIXME: Ugly!! + && $this->context->getUserData()->getIsAdminApp() === 0; } /** diff --git a/app/modules/web/themes/material-blue/views/_partials/fixed-header.inc b/app/modules/web/themes/material-blue/views/_partials/fixed-header.inc index e35dc0c0..85b8149a 100644 --- a/app/modules/web/themes/material-blue/views/_partials/fixed-header.inc +++ b/app/modules/web/themes/material-blue/views/_partials/fixed-header.inc @@ -55,7 +55,8 @@
  • security @@ -118,7 +119,8 @@ security diff --git a/app/modules/web/themes/material-blue/views/config/info.inc b/app/modules/web/themes/material-blue/views/config/info.inc index e86016da..9d7fe9b9 100644 --- a/app/modules/web/themes/material-blue/views/config/info.inc +++ b/app/modules/web/themes/material-blue/views/config/info.inc @@ -1,6 +1,8 @@
    info - +
    @@ -11,7 +13,7 @@ diff --git a/app/modules/web/themes/material-blue/views/main/upgrade.inc b/app/modules/web/themes/material-blue/views/main/upgrade.inc index 7d6bf66d..b7b52b25 100644 --- a/app/modules/web/themes/material-blue/views/main/upgrade.inc +++ b/app/modules/web/themes/material-blue/views/main/upgrade.inc @@ -1,7 +1,7 @@
    -

    @@ -29,7 +29,7 @@
    - 0): ?> + 0): ?>
    • @@ -110,7 +110,7 @@ - +
      • diff --git a/composer.json b/composer.json index f593ee55..7ec4fc68 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,8 @@ "require-dev": { "phpunit/phpunit": "^6", "phpunit/dbunit": "^3", - "symfony/debug" : "~v3.4" + "symfony/debug" : "~v3.4", + "fzaninotto/faker": "~v1.8" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 79b9197f..fa366102 100644 --- a/composer.lock +++ b/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": "c2cd9288ce46a66ef3ed3403602673d5", + "content-hash": "1ed25866adfa0121e573efaa183f435e", "packages": [ { "name": "ademarre/binary-to-text-php", @@ -1435,12 +1435,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "053766d789f6393e5bc0896635d35abf8d2d362e" + "reference": "b3569bbd5257d4ae0885b14feb6e45e41e8184b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/053766d789f6393e5bc0896635d35abf8d2d362e", - "reference": "053766d789f6393e5bc0896635d35abf8d2d362e", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/b3569bbd5257d4ae0885b14feb6e45e41e8184b7", + "reference": "b3569bbd5257d4ae0885b14feb6e45e41e8184b7", "shasum": "" }, "conflict": { @@ -1527,7 +1527,7 @@ "symfony/dependency-injection": ">=2,<2.0.17", "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2", - "symfony/http-foundation": ">=2,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", + "symfony/http-foundation": ">=2,<2.7.49|>=2.8,<2.8.44|>=3,<3.3.18|>=3.4,<3.4.14|>=4,<4.0.14|>=4.1,<4.1.3", "symfony/http-kernel": ">=2,<2.3.29|>=2.4,<2.5.12|>=2.6,<2.6.8", "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", "symfony/routing": ">=2,<2.0.19", @@ -1538,7 +1538,7 @@ "symfony/security-guard": ">=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", "symfony/serializer": ">=2,<2.0.11", - "symfony/symfony": ">=2,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", + "symfony/symfony": ">=2,<2.7.49|>=2.8,<2.8.44|>=3,<3.3.18|>=3.4,<3.4.14|>=4,<4.0.14|>=4.1,<4.1.3", "symfony/translation": ">=2,<2.0.17", "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3", "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", @@ -1564,9 +1564,10 @@ "zendframework/zend-captcha": ">=2,<2.4.9|>=2.5,<2.5.2", "zendframework/zend-crypt": ">=2,<2.4.9|>=2.5,<2.5.2", "zendframework/zend-db": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.10|>=2.3,<2.3.5", - "zendframework/zend-diactoros": ">=1,<1.0.4", + "zendframework/zend-diactoros": ">=1,<1.8.4", + "zendframework/zend-feed": ">=1,<2.10.3", "zendframework/zend-form": ">=2,<2.2.7|>=2.3,<2.3.1", - "zendframework/zend-http": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.3,<2.3.8|>=2.4,<2.4.1", + "zendframework/zend-http": ">=1,<2.8.1", "zendframework/zend-json": ">=2.1,<2.1.6|>=2.2,<2.2.6", "zendframework/zend-ldap": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.8|>=2.3,<2.3.3", "zendframework/zend-mail": ">=2,<2.4.11|>=2.5,<2.7.2", @@ -1597,7 +1598,7 @@ } ], "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", - "time": "2018-07-18T13:51:34+00:00" + "time": "2018-08-02T11:57:59+00:00" }, { "name": "symfony/polyfill-php56", @@ -1763,6 +1764,56 @@ ], "time": "2015-06-14T21:17:01+00:00" }, + { + "name": "fzaninotto/faker", + "version": "v1.8.0", + "source": { + "type": "git", + "url": "https://github.com/fzaninotto/Faker.git", + "reference": "f72816b43e74063c8b10357394b6bba8cb1c10de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/f72816b43e74063c8b10357394b6bba8cb1c10de", + "reference": "f72816b43e74063c8b10357394b6bba8cb1c10de", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "ext-intl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7", + "squizlabs/php_codesniffer": "^1.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "time": "2018-07-12T10:23:15+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.7.0", @@ -2428,16 +2479,16 @@ }, { "name": "phpunit/phpunit", - "version": "6.5.9", + "version": "6.5.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "093ca5508174cd8ab8efe44fd1dde447adfdec8f" + "reference": "5744955af9c0a2de74a5eb5287c50bf025100d39" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/093ca5508174cd8ab8efe44fd1dde447adfdec8f", - "reference": "093ca5508174cd8ab8efe44fd1dde447adfdec8f", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5744955af9c0a2de74a5eb5287c50bf025100d39", + "reference": "5744955af9c0a2de74a5eb5287c50bf025100d39", "shasum": "" }, "require": { @@ -2455,7 +2506,7 @@ "phpunit/php-file-iterator": "^1.4.3", "phpunit/php-text-template": "^1.2.1", "phpunit/php-timer": "^1.0.9", - "phpunit/phpunit-mock-objects": "^5.0.5", + "phpunit/phpunit-mock-objects": "^5.0.8", "sebastian/comparator": "^2.1", "sebastian/diff": "^2.0", "sebastian/environment": "^3.1", @@ -2508,7 +2559,7 @@ "testing", "xunit" ], - "time": "2018-07-03T06:40:40+00:00" + "time": "2018-08-03T05:27:14+00:00" }, { "name": "phpunit/phpunit-mock-objects", diff --git a/lib/SP/Bootstrap.php b/lib/SP/Bootstrap.php index 755967e7..ea174749 100644 --- a/lib/SP/Bootstrap.php +++ b/lib/SP/Bootstrap.php @@ -46,7 +46,6 @@ use SP\Services\Upgrade\UpgradeConfigService; use SP\Services\Upgrade\UpgradeUtil; use SP\Util\Checks; use SP\Util\Filter; -use SP\Util\Util; use Symfony\Component\Debug\Debug; defined('APP_ROOT') || die(); @@ -259,7 +258,7 @@ final class Bootstrap if (!self::$checkPhpVersion) { throw new InitializationException( - sprintf(__u('Versión de PHP requerida >= %s <= %s'), '7.0', '7.2'), + sprintf(__('Versión de PHP requerida >= %s <= %s'), '7.0', '7.2'), InitializationException::ERROR, __u('Actualice la versión de PHP para que la aplicación funcione correctamente') ); @@ -389,7 +388,7 @@ final class Bootstrap { if (file_exists(OLD_CONFIG_FILE)) { $upgradeConfigService = self::$container->get(UpgradeConfigService::class); - $upgradeConfigService->upgradeOldConfigFile(Util::getVersionStringNormalized()); + $upgradeConfigService->upgradeOldConfigFile(InitWeb::getVersionStringNormalized()); } $configVersion = UpgradeUtil::fixVersionNumber($this->configData->getConfigVersion()); diff --git a/lib/SP/Core/Crypt/Cookie.php b/lib/SP/Core/Crypt/Cookie.php index cb1d53d2..7e15ee47 100644 --- a/lib/SP/Core/Crypt/Cookie.php +++ b/lib/SP/Core/Crypt/Cookie.php @@ -34,14 +34,14 @@ use SP\Http\Request; */ abstract class Cookie { - /** - * @var string - */ - private $cookieName; /** * @var Request */ protected $request; + /** + * @var string + */ + private $cookieName; /** * Cookie constructor. @@ -63,11 +63,11 @@ abstract class Cookie * * @return string */ - protected final function sign($data, $cypher) + public final function sign($data, $cypher) { $data = base64_encode($data); - return hash_hmac('sha256', $data, $cypher) . ';' . $data; + return Hash::signMessage($data, $cypher) . ';' . $data; } /** @@ -78,15 +78,15 @@ abstract class Cookie * * @return bool|string */ - protected final function getCookieData($data, $cypher) + public final function getCookieData($data, $cypher) { - list($signature, $data) = explode(';', $data, 2); - - if (!empty($signature) && !empty($data)) { - return hash_equals($signature, hash_hmac('sha256', $data, $cypher)) ? base64_decode($data) : false; + if (strpos($data, ';') === false) { + return false; } - return false; + list($signature, $data) = explode(';', $data, 2); + + return Hash::checkMessage($data, $cypher, $signature) ? base64_decode($data) : false; } /** @@ -108,6 +108,17 @@ abstract class Cookie */ protected function setCookie($data) { + // Do not try to set cookies when testing + if (APP_MODULE === 'tests') { + return true; + } + + if (headers_sent()) { + logger('Headers already sent', 'ERROR'); + + return false; + } + return setcookie($this->cookieName, $data, 0, Bootstrap::$WEBROOT); } } \ No newline at end of file diff --git a/lib/SP/Core/Crypt/Hash.php b/lib/SP/Core/Crypt/Hash.php index 11bb9132..27273f26 100644 --- a/lib/SP/Core/Crypt/Hash.php +++ b/lib/SP/Core/Crypt/Hash.php @@ -82,6 +82,20 @@ final class Hash return password_hash(self::getKey($key, false), PASSWORD_BCRYPT); } + /** + * Checks a message with a given key against a hash + * + * @param $message + * @param $key + * @param $hash + * + * @return bool + */ + public static function checkMessage($message, $key, $hash) + { + return hash_equals($hash, self::signMessage($message, $key)); + } + /** * Signs a message with a given key * @@ -94,18 +108,4 @@ final class Hash { return hash_hmac('sha256', $message, $key); } - - /** - * Checks a message with a given key against a hash - * - * @param $message - * @param $key - * @param $hash - * - * @return bool - */ - public static function checkMessage($message, $key, $hash) - { - return hash_hmac('sha256', $message, $key) === $hash; - } } \ No newline at end of file diff --git a/lib/SP/Core/Crypt/SecureKeyCookie.php b/lib/SP/Core/Crypt/SecureKeyCookie.php index 274bcb0a..675318bc 100644 --- a/lib/SP/Core/Crypt/SecureKeyCookie.php +++ b/lib/SP/Core/Crypt/SecureKeyCookie.php @@ -44,7 +44,11 @@ final class SecureKeyCookie extends Cookie * * @var Key */ - protected $securedKey; + private $securedKey; + /** + * @var string + */ + private $cypher; /** * @param Request $request @@ -53,7 +57,20 @@ final class SecureKeyCookie extends Cookie */ public static function factory(Request $request) { - return new self(self::COOKIE_NAME, $request); + $self = new self(self::COOKIE_NAME, $request); + $self->cypher = $self->getCypher(); + + return $self; + } + + /** + * Devolver la llave de cifrado para los datos de la cookie + * + * @return string + */ + public function getCypher() + { + return sha1($this->request->getHeader('User-Agent') . $this->request->getClientAddress()); } /** @@ -63,85 +80,77 @@ final class SecureKeyCookie extends Cookie */ public function getKey() { - $key = $this->getCypher(); + $cookie = $this->getCookie(); - if (($cookie = $this->getCookie())) { - $data = $this->getCookieData($cookie, $key); + if ($cookie !== false) { + $data = $this->getCookieData($cookie, $this->cypher); - if ($data === false) { - logger('Cookie verification error.'); + if ($data !== false) { + /** @var Vault $vault */ + $vault = unserialize($data, ['allowed_classes' => Vault::class]); - return $this->saveKey($key); - } + if ($vault !== false + && ($vault instanceof Vault) === true + ) { + try { + $this->securedKey = Key::loadFromAsciiSafeString($vault->getData($this->cypher)); - /** @var Vault $vault */ - $vault = unserialize($data); - - if ($vault !== false - && ($vault instanceof Vault) === true - ) { - try { - return Key::loadFromAsciiSafeString($vault->getData($key)); - } catch (CryptoException $e) { - logger($e->getMessage()); + return $this->securedKey; + } catch (CryptoException $e) { + logger($e->getMessage(), 'EXCEPTION'); + } return false; } + } else { + logger('Cookie verification error', 'ERROR'); } - } elseif (($this->getSecuredKey() instanceof Key) === true) { - return $this->getSecuredKey(); - } else { - return $this->saveKey($key); + } elseif (($this->securedKey instanceof Key) === true) { + return $this->securedKey; } - return false; - } - - /** - * Devolver la llave de cifrado para los datos de la cookie - * - * @return string - */ - private function getCypher() - { - return md5($this->request->getHeader('User-Agent') . $this->request->getClientAddress()); + return $this->saveKey() ? $this->securedKey : false; } /** * Guardar una llave de encriptación * - * @param $key - * * @return Key|false */ - public function saveKey($key) + public function saveKey() { - if (empty($key)) { - return false; - } - try { - $this->securedKey = Key::createNewRandomKey(); - - $vault = new Vault(); - $vault->saveData($this->securedKey->saveToAsciiSafeString(), $key); - - if ($this->setCookie($this->sign(serialize($vault), $key))) { - logger('Generating a new session key.'); - - return $this->securedKey; - } else { - logger('Could not generate session key cookie.'); + if ($this->setCookie($this->sign($this->generateSecuredData()->getSerialized(), $this->cypher)) === false) { + logger('Could not generate session\'s key cookie', 'ERROR'); unset($this->securedKey); + + return false; } + + logger('Generating a new session\'s key cookie'); + + return true; } catch (CryptoException $e) { - logger($e->getMessage()); + logger($e->getMessage(), 'EXCEPTION'); } return false; } + /** + * @return Vault + * @throws CryptoException + * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException + */ + public function generateSecuredData() + { + $this->securedKey = Key::createNewRandomKey(); + + return (new Vault()) + ->saveData($this->securedKey->saveToAsciiSafeString(), $this->cypher); + } + /** * @return Key */ diff --git a/lib/SP/DataModel/AccountData.php b/lib/SP/DataModel/AccountData.php index 2f79e6f0..e3b5c7ca 100644 --- a/lib/SP/DataModel/AccountData.php +++ b/lib/SP/DataModel/AccountData.php @@ -27,7 +27,7 @@ namespace SP\DataModel; defined('APP_ROOT') || die(); use JsonSerializable; -use SP\Util\Json; +use SP\Http\Json; /** * Class AccountData diff --git a/lib/SP/Html/Html.php b/lib/SP/Html/Html.php index 2f5cac0e..ac054586 100644 --- a/lib/SP/Html/Html.php +++ b/lib/SP/Html/Html.php @@ -91,32 +91,6 @@ final class Html return $data; } - /** - * Limpia los datos recibidos de un formulario. Sölo admite cadenas - * - * @param $data - * - * @return false|string con los datos limpiados - */ - public static function sanitizeFull(&$data) - { - if (empty($data)) { - return $data; - } - - if (is_array($data)) { - array_walk_recursive($data, '\SP\Html\Html::sanitizeFull'); - } else { - $data = preg_replace([ - /** @lang RegExp */ - '/x[a-z\d]+/i', - /** @lang RegExp */ - '/[^a-z0-9]+/i'], '', urldecode($data)); - } - - return $data; - } - /** * Truncar un texto a una determinada longitud. * @@ -130,9 +104,10 @@ final class Html */ public static function truncate($text, $limit, $ellipsis = '...') { - if (strlen($text) > $limit) { - $text = trim(mb_substr($text, 0, $limit)) . $ellipsis; + if (mb_strlen($text) > $limit) { + return trim(mb_substr($text, 0, $limit)) . $ellipsis; } + return $text; } @@ -144,14 +119,15 @@ final class Html * * @return string */ - public static function rgb2hex($rgb) + public static function rgb2hex(array $rgb) { $hex = "#"; - $hex .= str_pad(dechex($rgb[0]), 2, "0", STR_PAD_LEFT); - $hex .= str_pad(dechex($rgb[1]), 2, "0", STR_PAD_LEFT); - $hex .= str_pad(dechex($rgb[2]), 2, "0", STR_PAD_LEFT); - return $hex; // returns the hex value including the number sign (#) + foreach ($rgb as $val) { + $hex .= str_pad(dechex($val), 2, "0", STR_PAD_LEFT); + } + + return $hex; } /** @@ -163,7 +139,7 @@ final class Html */ public static function strongText($text) { - return ('' . $text . ''); + return '' . $text . ''; } /** @@ -176,13 +152,11 @@ final class Html * * @return string */ - public static function anchorText($text, $link = '', $title = '', $attribs = '') + public static function anchorText($text, $link = null, $title = null, $attribs = '') { - $alink = (!empty($link)) ? $link : $text; - $atitle = (!empty($title)) ? $title : ''; + $alink = $link !== null ? $link : $text; + $atitle = $title !== null ? $title : $text; - $anchor = '' . $text . ''; - - return $anchor; + return sprintf('%s', $alink, $atitle, $attribs, $text); } } diff --git a/lib/SP/Html/Minify.php b/lib/SP/Html/Minify.php index d2bc9bee..a7c7fda8 100644 --- a/lib/SP/Html/Minify.php +++ b/lib/SP/Html/Minify.php @@ -109,10 +109,6 @@ final class Minify $this->setHeaders(); -// if ($this->checkZlib() || !ob_start('ob_gzhandler')) { -// ob_start(); -// } - $data = ''; foreach ($this->files as $file) { @@ -123,12 +119,12 @@ final class Minify try { $data .= '/* URL: ' . $file['name'] . ' */' . PHP_EOL . Util::getDataFromUrl($file['name']); } catch (SPException $e) { - logger($e->getMessage()); + processException($e); } } else { - if ($file['min'] === true && $disableMinify === false) { $data .= '/* MINIFIED FILE: ' . $file['name'] . ' */' . PHP_EOL; + if ($this->type === self::FILETYPE_JS) { $data .= $this->jsCompress(file_get_contents($filePath)); } @@ -167,10 +163,13 @@ final class Minify $response->header('Pragma', 'public; maxage={' . self::OFFSET . '}'); $response->header('Expires', gmdate('D, d M Y H:i:s \G\M\T', time() + self::OFFSET)); - if ($this->type === self::FILETYPE_JS) { - $response->header('Content-type', 'application/x-javascript; charset: UTF-8'); - } elseif ($this->type === self::FILETYPE_CSS) { - $response->header('Content-type', 'text/css; charset: UTF-8'); + switch ($this->type) { + case self::FILETYPE_JS; + $response->header('Content-type', 'application/javascript; charset: UTF-8'); + break; + case self::FILETYPE_CSS: + $response->header('Content-type', 'text/css; charset: UTF-8'); + break; } } @@ -199,15 +198,15 @@ final class Minify */ private function jsCompress($buffer) { - $regexReplace = array( + $regexReplace = [ '#/\*[^*]*\*+([^/][^*]*\*+)*/#', '#^[\s\t]*//.*$#m', '#[\s\t]+$#m', '#^[\s\t]+#m', '#\s*//\s.*$#m' - ); + ]; - return str_replace(array("\r\n", "\r", "\n", "\t"), '', preg_replace($regexReplace, '', $buffer)); + return str_replace(["\r\n", "\r", "\n", "\t"], '', preg_replace($regexReplace, '', $buffer)); } /** @@ -241,26 +240,18 @@ final class Minify */ public function addFile($file, $minify = true) { - if (strrpos($file, ',')) { - $files = explode(',', $file); + $filePath = $this->base . DIRECTORY_SEPARATOR . $file; - foreach ($files as $filename) { - $this->addFile($filename, $minify); - } + if (file_exists($filePath)) { + $this->files[] = [ + 'type' => 'file', + 'base' => $this->base, + 'name' => Request::getSecureAppFile($file, $this->base), + 'min' => $minify === true && $this->needsMinify($file), + 'md5' => md5_file($filePath) + ]; } else { - $filePath = $this->base . DIRECTORY_SEPARATOR . $file; - - if (file_exists($filePath)) { - $this->files[] = array( - 'type' => 'file', - 'base' => $this->base, - 'name' => Request::getSecureAppFile($file, $this->base), - 'min' => $minify === true && $this->needsMinify($file), - 'md5' => md5_file($filePath) - ); - } else { - logger('File not found: ' . $filePath); - } + logger('File not found: ' . $filePath); } return $this; @@ -275,7 +266,7 @@ final class Minify */ private function needsMinify($file) { - return !preg_match('/\.(min|pack)\./', $file); + return !preg_match('/\.min|pack\.css|js/', $file); } /** @@ -347,15 +338,4 @@ final class Minify return $this; } - - /** - * Comprobar si la salida comprimida en con zlib está activada. - * No es compatible con ob_gzhandler() - * - * @return bool - */ - private function checkZlib() - { - return Util::boolval(ini_get('zlib.output_compression')); - } } \ No newline at end of file diff --git a/lib/SP/Http/Client.php b/lib/SP/Http/Client.php index 77605072..dd00018f 100644 --- a/lib/SP/Http/Client.php +++ b/lib/SP/Http/Client.php @@ -31,7 +31,7 @@ use SP\Config\ConfigData; * * @package SP\Http */ -class Client +final class Client { /** * @param ConfigData $configData diff --git a/lib/SP/Http/Cookies.php b/lib/SP/Http/Cookies.php deleted file mode 100644 index 2cfde2f7..00000000 --- a/lib/SP/Http/Cookies.php +++ /dev/null @@ -1,43 +0,0 @@ -. - */ - -namespace SP\Http; - -/** - * Class Cookies - * - * @package SP\Http - */ -final class Cookies -{ - /** - * Comprueba si las cookies están habilitadas - * - * @return bool - */ - public static function checkCookies() - { - return isset($_COOKIE[session_name()]); - } -} \ No newline at end of file diff --git a/lib/SP/Util/Json.php b/lib/SP/Http/Json.php similarity index 55% rename from lib/SP/Util/Json.php rename to lib/SP/Http/Json.php index 00448602..eee09bf8 100644 --- a/lib/SP/Util/Json.php +++ b/lib/SP/Http/Json.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. @@ -22,10 +22,12 @@ * along with sysPass. If not, see . */ -namespace SP\Util; +namespace SP\Http; +use Klein\Klein; +use Klein\Response; +use SP\Bootstrap; use SP\Core\Exceptions\SPException; -use SP\Http\JsonResponse; /** @@ -35,57 +37,42 @@ use SP\Http\JsonResponse; */ final class Json { + const SAFE = [ + 'from' => ['\\', '"', '\''], + 'to' => ['\\', '\"', '\\\''] + ]; + /** - * Devuelve una respuesta en formato JSON con el estado y el mensaje. - * - * @param JsonResponse $JsonResponse - * - * @return void + * @var Response */ - public static function returnJson(JsonResponse $JsonResponse) + private $response; + + /** + * Json constructor. + * + * @param Response $response + */ + public function __construct(Response $response) { - header('Content-type: application/json; charset=utf-8'); - - try { - exit(self::getJson($JsonResponse)); - } catch (SPException $e) { - $JsonResponse = new JsonResponse(); - $JsonResponse->setDescription($e->getMessage()); - $JsonResponse->addMessage($e->getHint()); - - exit(json_encode($JsonResponse)); - } + $this->response = $response; } /** - * Devuelve una cadena en formato JSON + * @param Response $response * - * @param $data - * - * @return string La cadena en formato JSON - * @throws \SP\Core\Exceptions\SPException + * @return Json */ - public static function getJson($data) + public static function factory(Response $response) { - $json = json_encode($data, JSON_PARTIAL_OUTPUT_ON_ERROR); - - if ($json === false) { - throw new SPException(__u('Error de codificación'), SPException::CRITICAL, json_last_error_msg()); - } - - return $json; + return new self($response); } /** - * Devuelve una respuesta en formato JSON - * - * @param string $data JSON string + * @return Json */ - public static function returnRawJson($data) + public static function fromDic() { - header('Content-type: application/json; charset=utf-8'); - - exit($data); + return new self(Bootstrap::getContainer()->get(Klein::class)->response()); } /** @@ -101,9 +88,9 @@ final class Json array_walk_recursive($data, function (&$value) { if (is_object($value)) { - foreach ($value as &$attribute) { - if (is_string($attribute) && $attribute !== '') { - self::safeJsonString($attribute); + foreach ($value as $property => $v) { + if (is_string($v) && $v !== '') { + $value->$property = self::safeJsonString($v); } } @@ -131,13 +118,67 @@ final class Json * * @return mixed */ - public static function safeJsonString(&$string) + public static function safeJsonString($string) { - $strFrom = ['\\', '"', '\'']; - $strTo = ['\\', '\"', '\\\'']; + return str_replace(self::SAFE['from'], self::SAFE['to'], $string); + } - $string = str_replace($strFrom, $strTo, $string); + /** + * Devuelve una respuesta en formato JSON + * + * @param string $data JSON string + */ + public function returnRawJson($data) + { + $this->response + ->header('Content-type', 'application/json; charset=utf-8') + ->body($data) + ->send(true); + } - return $string; + /** + * Devuelve una respuesta en formato JSON con el estado y el mensaje. + * + * @param JsonResponse $jsonResponse + * + * @return void + */ + public function returnJson(JsonResponse $jsonResponse) + { + $this->response->header('Content-type', 'application/json; charset=utf-8'); + + try { + $this->response->body(self::getJson($jsonResponse)); + } catch (SPException $e) { + $jsonResponse = new JsonResponse($e->getMessage()); + $jsonResponse->addMessage($e->getHint()); + + $this->response->body(json_encode($jsonResponse)); + } + + $this->response->send(true); + } + + /** + * Devuelve una cadena en formato JSON + * + * @param $data + * + * @return string La cadena en formato JSON + * @throws \SP\Core\Exceptions\SPException + */ + public static function getJson($data) + { + $json = json_encode($data, JSON_PARTIAL_OUTPUT_ON_ERROR); + + if ($json === false || json_last_error() !== JSON_ERROR_NONE) { + throw new SPException( + __u('Error de codificación'), + SPException::CRITICAL, + json_last_error_msg() + ); + } + + return $json; } } \ No newline at end of file diff --git a/lib/SP/Http/JsonResponse.php b/lib/SP/Http/JsonResponse.php index 031ee598..24bdca16 100644 --- a/lib/SP/Http/JsonResponse.php +++ b/lib/SP/Http/JsonResponse.php @@ -68,6 +68,16 @@ final class JsonResponse implements \JsonSerializable */ protected $csrf = ''; + /** + * JsonResponse constructor. + * + * @param string $description + */ + public function __construct(string $description = null) + { + $this->description = $description; + } + /** * @return int */ diff --git a/lib/SP/Http/Request.php b/lib/SP/Http/Request.php index 94692ec4..02dc74eb 100644 --- a/lib/SP/Http/Request.php +++ b/lib/SP/Http/Request.php @@ -512,6 +512,6 @@ final class Request */ public function getServer(string $key): string { - return $this->request->server()->get($key, ''); + return (string)$this->request->server()->get($key, ''); } } \ No newline at end of file diff --git a/lib/SP/Http/Response.php b/lib/SP/Http/Response.php deleted file mode 100644 index fe833f98..00000000 --- a/lib/SP/Http/Response.php +++ /dev/null @@ -1,102 +0,0 @@ -. - */ - -namespace SP\Http; - -use SP\Core\Exceptions\SPException; -use SP\Util\Json; - -defined('APP_ROOT') || die(); - -/** - * Esta clase es encargada de ejecutar acciones comunes para las funciones - */ -final class Response -{ - /** - * Devuelve una respuesta en formato XML con el estado y el mensaje. - * - * @param string $description mensaje a devolver - * @param int $status devuelve el estado - * - * @return bool - */ - public static function printXml($description, $status = 1) - { - if (!is_string($description)) { - return false; - } - - $arrStrFrom = ['&', '<', '>', '"', "\'"]; - $arrStrTo = ['&', '<', '>', '"', ''']; - - $cleanDescription = str_replace($arrStrFrom, $arrStrTo, $description); - - $xml[] = ''; - $xml[] = ''; - $xml[] = '' . $status . ''; - $xml[] = '' . $cleanDescription . ''; - $xml[] = ''; - - header('Content-Type: application/xml'); - exit(implode(PHP_EOL, $xml)); - } - - /** - * Devuelve una respuesta en formato JSON con el estado y el mensaje. - * - * @param string|array $data mensaje a devolver - * @param int $status devuelve el estado - * @param string $action con la accion a realizar - */ - public static function printJson($data, $status = 1, $action = '') - { - if (!is_array($data)) { - $json = [ - 'status' => $status, - 'description' => $data, - 'action' => $action - ]; - } else { - $data['status'] = $status; - $data['action'] = $action; - $json = $data; - } - - header('Content-type: application/json; charset=utf-8'); - - try { - exit(Json::getJson($json)); - } catch (SPException $e) { - $data['status'] = 1; - $data['description'] = __($e->getMessage()); - - if (isset($data['html'])) { - $data['html'] = __($e->getMessage()); - } - - exit(json_encode($data)); - } - } -} \ No newline at end of file diff --git a/lib/SP/Http/XMLRPCResponseParse.php b/lib/SP/Http/XMLRPCResponseParse.php index 5c29e0f0..3bb9f599 100644 --- a/lib/SP/Http/XMLRPCResponseParse.php +++ b/lib/SP/Http/XMLRPCResponseParse.php @@ -66,7 +66,7 @@ abstract class XMLRPCResponseParse $dom->loadXML($xml); if ($dom->getElementsByTagName('methodResponse')->length === 0) { - throw new \DOMException(__('Respuesta XML-RPC inválida', false)); + throw new \DOMException(__u('Respuesta XML-RPC inválida')); } $this->root = $dom->documentElement; diff --git a/lib/SP/Http/Xml.php b/lib/SP/Http/Xml.php new file mode 100644 index 00000000..90e5f6f2 --- /dev/null +++ b/lib/SP/Http/Xml.php @@ -0,0 +1,89 @@ +. + */ + +namespace SP\Http; + +use Klein\Response; + +/** + * Class Xml + * + * @package SP\Http + */ +final class Xml +{ + const SAFE = [ + 'from' => ['&', '<', '>', '"', "\'"], + 'to' => ['&', '<', '>', '"', '''] + ]; + + /** + * @var Response + */ + private $response; + + /** + * Xml constructor. + * + * @param Response $response + */ + public function __construct(Response $response) + { + $this->response = $response; + } + + /** + * Devuelve una respuesta en formato XML con el estado y el mensaje. + * + * @param string $description mensaje a devolver + * @param int $status devuelve el estado + */ + public function printXml(string $description, int $status = 1) + { + if (!is_string($description)) { + return; + } + + $xml[] = ''; + $xml[] = ''; + $xml[] = '' . $status . ''; + $xml[] = '' . $this->safeString($description) . ''; + $xml[] = ''; + + $this->response + ->header('Content-Type', 'application/xml') + ->body(implode(PHP_EOL, $xml)) + ->send(true); + } + + /** + * @param string $string + * + * @return mixed + */ + public function safeString(string $string) + { + return str_replace(self::SAFE['from'], self::SAFE['to'], $string); + } +} \ No newline at end of file diff --git a/lib/SP/Mvc/Controller/ControllerTrait.php b/lib/SP/Mvc/Controller/ControllerTrait.php index 8b044dac..bfabd6e7 100644 --- a/lib/SP/Mvc/Controller/ControllerTrait.php +++ b/lib/SP/Mvc/Controller/ControllerTrait.php @@ -26,10 +26,10 @@ namespace SP\Mvc\Controller; use SP\Core\Context\ContextInterface; use SP\Core\Exceptions\SPException; +use SP\Http\Json; use SP\Http\JsonResponse; use SP\Http\Request; use SP\Http\Uri; -use SP\Util\Json; use SP\Util\Util; @@ -61,11 +61,10 @@ trait ControllerTrait { if (!$context->isLoggedIn()) { if ($request->isJson()) { - $JsonResponse = new JsonResponse(); - $JsonResponse->setDescription(__u('La sesión no se ha iniciado o ha caducado')); - $JsonResponse->setStatus(10); + $jsonResponse = new JsonResponse(__u('La sesión no se ha iniciado o ha caducado')); + $jsonResponse->setStatus(10); - Json::returnJson($JsonResponse); + Json::fromDic()->returnJson($jsonResponse); } elseif ($request->isAjax()) { Util::logout(); } else { @@ -112,6 +111,6 @@ trait ControllerTrait */ protected function invalidAction() { - Json::returnJson((new JsonResponse())->setDescription(__u('Acción Inválida'))); + Json::fromDic()->returnJson(new JsonResponse(__u('Acción Inválida'))); } } \ No newline at end of file diff --git a/lib/SP/Mvc/View/Components/SelectItemAdapter.php b/lib/SP/Mvc/View/Components/SelectItemAdapter.php index 0b63cc66..dd39c9be 100644 --- a/lib/SP/Mvc/View/Components/SelectItemAdapter.php +++ b/lib/SP/Mvc/View/Components/SelectItemAdapter.php @@ -26,7 +26,7 @@ namespace SP\Mvc\View\Components; use RuntimeException; use SP\DataModel\DataModelInterface; -use SP\Util\Json; +use SP\Http\Json; /** * Class SelectItemAdapter diff --git a/lib/SP/Plugin/PluginManager.php b/lib/SP/Plugin/PluginManager.php index b79347e2..cfb3cf90 100644 --- a/lib/SP/Plugin/PluginManager.php +++ b/lib/SP/Plugin/PluginManager.php @@ -134,7 +134,7 @@ class PluginManager /** * Devolver los plugins habilitados * - * @return PluginInterface[] + * @return array * @throws \SP\Core\Exceptions\ConstraintException * @throws \SP\Core\Exceptions\QueryException */ diff --git a/lib/SP/Providers/Auth/Ldap/LdapBase.php b/lib/SP/Providers/Auth/Ldap/LdapBase.php index 604ce605..19e767f4 100644 --- a/lib/SP/Providers/Auth/Ldap/LdapBase.php +++ b/lib/SP/Providers/Auth/Ldap/LdapBase.php @@ -160,7 +160,7 @@ abstract class LdapBase implements LdapInterface, AuthInterface $this->eventDispatcher->notifyEvent('ldap.connection', new Event($this, EventMessage::factory() ->addDescription(__u('No es posible conectar con el servidor de LDAP')) - ->addDetail(__('Servidor'), $this->server) + ->addDetail(__u('Servidor'), $this->server) ->addDetail('LDAP ERROR', $this->getLdapErrorMessage())) ); @@ -399,13 +399,18 @@ abstract class LdapBase implements LdapInterface, AuthInterface { $searchResults = $this->getResults($this->getUserDnFilter(), self::SEARCH_ATTRIBUTES); - if ($searchResults === false || $searchResults['count'] > 1) { + if ($searchResults === false + || $searchResults['count'] > 1 + ) { + $count = $searchResults !== false ? $searchResults['count'] : 0; + $this->eventDispatcher->notifyEvent('ldap.getAttributes', new Event($this, EventMessage::factory() ->addDescription(__u('Error al localizar el usuario en LDAP')) ->addDetail(__u('Usuario'), $this->userLogin) ->addDetail('LDAP ERROR', $this->getLdapErrorMessage()) - ->addDetail('LDAP DN', $this->getGroupMembershipFilter())) + ->addDetail('LDAP DN', $this->getGroupMembershipFilter()) + ->addDetail('COUNT', $count)) ); throw new LdapException(__u('Error al localizar el usuario en LDAP')); @@ -470,18 +475,26 @@ abstract class LdapBase implements LdapInterface, AuthInterface */ protected function searchGroupDN() { - $group = $this->getGroupName() ?: $this->ldapParams->getGroup(); - $filter = '(cn=' . ldap_escape($group) . ')'; + if (strpos($this->ldapParams->getGroup(), 'cn=') === 0) { + $filter = $this->ldapParams->getGroup(); + } else { + $filter = '(cn=' . ldap_escape($this->ldapParams->getGroup()) . ')'; + } $searchResults = $this->getResults($filter, ['dn', 'cn']); - if ($searchResults === false || $searchResults['count'] > 1) { + if ($searchResults === false + || $searchResults['count'] > 1 + ) { + $count = $searchResults !== false ? $searchResults['count'] : 0; + $this->eventDispatcher->notifyEvent('ldap.search.group', new Event($this, EventMessage::factory() ->addDescription(__u('Error al buscar RDN de grupo')) ->addDetail(__u('Grupo'), $filter) ->addDetail('LDAP ERROR', $this->getLdapErrorMessage()) - ->addDetail('LDAP FILTER', $filter)) + ->addDetail('LDAP FILTER', $filter) + ->addDetail('COUNT', $count)) ); throw new LdapException(__u('Error al buscar RDN de grupo')); @@ -490,22 +503,6 @@ abstract class LdapBase implements LdapInterface, AuthInterface return $searchResults[0]['dn']; } - /** - * Obtener el nombre del grupo a partir del CN - * - * @return bool - */ - protected function getGroupName() - { - if ($this->ldapParams->getGroup() - && preg_match('/^cn=(?P[\w\s-]+)(?:,.*)?/i', $this->ldapParams->getGroup(), $matches) - ) { - return $matches['groupname']; - } - - return false; - } - /** * Buscar al usuario en un grupo. * @@ -607,6 +604,22 @@ abstract class LdapBase implements LdapInterface, AuthInterface return $this->isBound; } + /** + * Obtener el nombre del grupo a partir del CN + * + * @return bool + */ + protected function getGroupName() + { + if ($this->ldapParams->getGroup() + && preg_match('/^cn=(?P[\w\s-]+)(?:,.*)?/i', $this->ldapParams->getGroup(), $matches) + ) { + return $matches['groupname']; + } + + return false; + } + /** * Realizar la desconexión del servidor de LDAP. */ diff --git a/lib/SP/Providers/Auth/Ldap/LdapMsAds.php b/lib/SP/Providers/Auth/Ldap/LdapMsAds.php index 11c59927..9533b93f 100644 --- a/lib/SP/Providers/Auth/Ldap/LdapMsAds.php +++ b/lib/SP/Providers/Auth/Ldap/LdapMsAds.php @@ -36,8 +36,8 @@ use SP\Core\Events\EventMessage; */ final class LdapMsAds extends LdapBase { - const userObjectFilter = '(|(objectCategory=person)(objectClass=user))'; - const groupObjectFilter = '(objectCategory=group)'; + const FILTER_USER_OBJECT = '(|(objectCategory=person)(objectClass=user))'; + const FILTER_GROUP_OBJECT = '(objectCategory=group)'; /** * Devolver el filtro para comprobar la pertenecia al grupo @@ -48,12 +48,12 @@ final class LdapMsAds extends LdapBase protected function getGroupMembershipFilter() { if (empty($this->ldapParams->getGroup())) { - return self::userObjectFilter; + return self::FILTER_USER_OBJECT; } $groupDN = ldap_escape($this->searchGroupDN()); - return '(&(|(memberOf=' . $groupDN . ')(groupMembership=' . $groupDN . ')(memberof:1.2.840.113556.1.4.1941:=' . $groupDN . '))' . self::userObjectFilter . ')'; + return '(&(|(memberOf=' . $groupDN . ')(groupMembership=' . $groupDN . ')(memberof:1.2.840.113556.1.4.1941:=' . $groupDN . '))' . self::FILTER_USER_OBJECT . ')'; } /** @@ -103,7 +103,7 @@ final class LdapMsAds extends LdapBase { $userLogin = ldap_escape($this->userLogin); - return '(&(|(samaccountname=' . $userLogin . ')(cn=' . $userLogin . ')(uid=' . $userLogin . '))' . self::userObjectFilter . ')'; + return '(&(|(samaccountname=' . $userLogin . ')(cn=' . $userLogin . ')(uid=' . $userLogin . '))' . self::FILTER_USER_OBJECT . ')'; } /** @@ -189,6 +189,6 @@ final class LdapMsAds extends LdapBase */ protected function getGroupObjectFilter() { - return self::groupObjectFilter; + return self::FILTER_GROUP_OBJECT; } } \ No newline at end of file diff --git a/lib/SP/Providers/Auth/Ldap/LdapStd.php b/lib/SP/Providers/Auth/Ldap/LdapStd.php index 786c7da0..4db02273 100644 --- a/lib/SP/Providers/Auth/Ldap/LdapStd.php +++ b/lib/SP/Providers/Auth/Ldap/LdapStd.php @@ -36,8 +36,8 @@ use SP\Core\Events\EventMessage; */ final class LdapStd extends LdapBase { - const userObjectFilter = '(|(objectClass=inetOrgPerson)(objectClass=person)(objectClass=simpleSecurityObject))'; - const groupObjectFilter = '(|(objectClass=groupOfNames)(objectClass=groupOfUniqueNames)(objectClass=group))'; + const FILTER_USER_OBJECT = '(|(objectClass=inetOrgPerson)(objectClass=person)(objectClass=simpleSecurityObject))'; + const FILTER_GROUP_OBJECT = '(|(objectClass=groupOfNames)(objectClass=groupOfUniqueNames)(objectClass=group))'; /** * Devolver el filtro para comprobar la pertenecia al grupo @@ -48,12 +48,12 @@ final class LdapStd extends LdapBase protected function getGroupMembershipFilter() { if (empty($this->ldapParams->getGroup())) { - return self::userObjectFilter; + return self::FILTER_USER_OBJECT; } $groupDN = ldap_escape($this->searchGroupDN()); - return '(&(|(memberOf=' . $groupDN . ')(groupMembership=' . $groupDN . '))' . self::userObjectFilter . ')'; + return '(&(|(memberOf=' . $groupDN . ')(groupMembership=' . $groupDN . '))' . self::FILTER_USER_OBJECT . ')'; } /** @@ -75,7 +75,7 @@ final class LdapStd extends LdapBase { $userLogin = ldap_escape($this->userLogin); - return '(&(|(samaccountname=' . $userLogin . ')(cn=' . $userLogin . ')(uid=' . $userLogin . '))' . self::userObjectFilter . ')'; + return '(&(|(samaccountname=' . $userLogin . ')(cn=' . $userLogin . ')(uid=' . $userLogin . '))' . self::FILTER_USER_OBJECT . ')'; } /** @@ -103,7 +103,7 @@ final class LdapStd extends LdapBase $userDN = ldap_escape($this->ldapAuthData->getDn()); $groupName = $this->getGroupName() ?: $this->ldapParams->getGroup(); - $filter = '(&(cn=' . ldap_escape($groupName) . ')(|(member=' . $userDN . ')(uniqueMember=' . $userDN . '))' . self::groupObjectFilter . ')'; + $filter = '(&(cn=' . ldap_escape($groupName) . ')(|(member=' . $userDN . ')(uniqueMember=' . $userDN . '))' . self::FILTER_GROUP_OBJECT . ')'; $searchResults = $this->getResults($filter, ['member', 'uniqueMember']); @@ -150,6 +150,6 @@ final class LdapStd extends LdapBase */ protected function getGroupObjectFilter() { - return self::groupObjectFilter; + return self::FILTER_GROUP_OBJECT; } } \ No newline at end of file diff --git a/lib/SP/Providers/Provider.php b/lib/SP/Providers/Provider.php index 82d16344..09185f69 100644 --- a/lib/SP/Providers/Provider.php +++ b/lib/SP/Providers/Provider.php @@ -24,7 +24,6 @@ namespace SP\Providers; -use DI\Container; use Psr\Container\ContainerInterface; use SP\Config\Config; use SP\Core\Context\ContextInterface; @@ -51,22 +50,14 @@ abstract class Provider * @var EventDispatcher */ protected $eventDispatcher; - /** - * @var ContainerInterface - */ - private $dic; /** * Provider constructor. * - * @param Container $dic - * - * @throws \DI\DependencyException - * @throws \DI\NotFoundException + * @param ContainerInterface $dic */ - final public function __construct(Container $dic) + final public function __construct(ContainerInterface $dic) { - $this->dic = $dic; $this->config = $dic->get(Config::class); $this->context = $dic->get(ContextInterface::class); $this->eventDispatcher = $dic->get(EventDispatcher::class); diff --git a/lib/SP/Services/Account/AccountFileService.php b/lib/SP/Services/Account/AccountFileService.php index 444584b2..bd907137 100644 --- a/lib/SP/Services/Account/AccountFileService.php +++ b/lib/SP/Services/Account/AccountFileService.php @@ -24,7 +24,8 @@ namespace SP\Services\Account; -use SP\Core\Exceptions\SPException; +use SP\Core\Exceptions\CheckException; +use SP\Core\Exceptions\InvalidImageException; use SP\DataModel\FileData; use SP\DataModel\FileExtData; use SP\DataModel\ItemSearchData; @@ -54,14 +55,21 @@ final class AccountFileService extends Service * @param FileData $itemData * * @return int - * @throws SPException + * @throws InvalidImageException * @throws \SP\Core\Exceptions\ConstraintException * @throws \SP\Core\Exceptions\QueryException */ public function create($itemData) { if (FileUtil::isImage($itemData)) { - $itemData->setThumb(ImageUtil::createThumbnail($itemData->getContent()) ?: 'no_thumb'); + try { + $imageUtil = $this->dic->get(ImageUtil::class); + $itemData->setThumb($imageUtil->createThumbnail($itemData->getContent())); + } catch (CheckException $e) { + processException($e); + + $itemData->setThumb('no_thumb'); + } } else { $itemData->setThumb('no_thumb'); } diff --git a/lib/SP/Services/Api/JsonRpcResponse.php b/lib/SP/Services/Api/JsonRpcResponse.php index d0aaf4c4..c8bb421f 100644 --- a/lib/SP/Services/Api/JsonRpcResponse.php +++ b/lib/SP/Services/Api/JsonRpcResponse.php @@ -25,7 +25,7 @@ namespace SP\Services\Api; use SP\Core\Exceptions\SPException; -use SP\Util\Json; +use SP\Http\Json; /** * Class JsonRpcResponse diff --git a/lib/SP/Services/Crypt/SecureSessionService.php b/lib/SP/Services/Crypt/SecureSessionService.php index 8df0c05f..3489c41f 100644 --- a/lib/SP/Services/Crypt/SecureSessionService.php +++ b/lib/SP/Services/Crypt/SecureSessionService.php @@ -77,7 +77,7 @@ final class SecureSessionService extends Service try { if ($this->fileCache->isExpired($this->getFileNameFromCookie(), self::CACHE_EXPIRE_TIME)) { - logger('Session key expired or does not exist.'); + logger('Session key expired or does not exist', 'ERROR'); return $this->saveKey(); } @@ -106,7 +106,7 @@ final class SecureSessionService extends Service if (($uuid = $this->cookie->loadCookie($this->seed)) === false && ($uuid = $this->cookie->createCookie($this->seed)) === false ) { - throw new ServiceException('Unable to get UUID for filename.'); + throw new ServiceException('Unable to get UUID for filename'); } $this->filename = self::CACHE_PATH . DIRECTORY_SEPARATOR . $uuid; @@ -126,7 +126,7 @@ final class SecureSessionService extends Service $securedKey = Key::createNewRandomKey(); $this->fileCache->save($this->getFileNameFromCookie(), (new Vault())->saveData($securedKey->saveToAsciiSafeString(), $this->getCypher())); - logger('Saved session key.'); + logger('Saved session key'); return $securedKey; } catch (\Exception $e) { diff --git a/lib/SP/Services/Export/XmlExportService.php b/lib/SP/Services/Export/XmlExportService.php index c2857e9c..03f91045 100644 --- a/lib/SP/Services/Export/XmlExportService.php +++ b/lib/SP/Services/Export/XmlExportService.php @@ -41,6 +41,7 @@ use SP\Services\Service; use SP\Services\ServiceException; use SP\Services\Tag\TagService; use SP\Util\Util; +use SP\Util\Version; defined('APP_ROOT') || die(); @@ -194,7 +195,7 @@ final class XmlExportService extends Service $nodeMeta = $this->xml->createElement('Meta'); $metaGenerator = $this->xml->createElement('Generator', 'sysPass'); - $metaVersion = $this->xml->createElement('Version', Util::getVersionStringNormalized()); + $metaVersion = $this->xml->createElement('Version', Version::getVersionStringNormalized()); $metaTime = $this->xml->createElement('Time', time()); $metaUser = $this->xml->createElement('User', $userData->getLogin()); $metaUser->setAttribute('id', $userData->getId()); diff --git a/lib/SP/Services/Import/ImportService.php b/lib/SP/Services/Import/ImportService.php index 8523aec2..22406291 100644 --- a/lib/SP/Services/Import/ImportService.php +++ b/lib/SP/Services/Import/ImportService.php @@ -34,6 +34,8 @@ defined('APP_ROOT') || die(); */ final class ImportService extends Service { + const ALLOWED_EXTS = ['CSV', 'XML']; + /** * @var ImportParams */ diff --git a/lib/SP/Services/Install/Installer.php b/lib/SP/Services/Install/Installer.php index 97918a4a..490931a7 100644 --- a/lib/SP/Services/Install/Installer.php +++ b/lib/SP/Services/Install/Installer.php @@ -41,6 +41,7 @@ use SP\Services\UserGroup\UserGroupService; use SP\Services\UserProfile\UserProfileService; use SP\Storage\Database\DBStorageInterface; use SP\Util\Util; +use SP\Util\Version; defined('APP_ROOT') || die(); @@ -54,7 +55,7 @@ final class Installer extends Service */ const VERSION = [3, 0, 0]; const VERSION_TEXT = '3.0-beta'; - const BUILD = 18080201; + const BUILD = 18080601; /** * @var DatabaseSetupInterface @@ -182,7 +183,7 @@ final class Installer extends Service $this->saveMasterPassword(); $this->createAdminAccount(); - $version = Util::getVersionStringNormalized(); + $version = Version::getVersionStringNormalized(); $this->dic->get(ConfigService::class) ->create(new \SP\DataModel\ConfigData('version', $version)); @@ -235,8 +236,8 @@ final class Installer extends Service $this->configData->setPasswordSalt(Util::generateRandomBytes(30)); // Sets version and remove upgrade key - $this->configData->setConfigVersion(Util::getVersionStringNormalized()); - $this->configData->setDatabaseVersion(Util::getVersionStringNormalized()); + $this->configData->setConfigVersion(Version::getVersionStringNormalized()); + $this->configData->setDatabaseVersion(Version::getVersionStringNormalized()); $this->configData->setUpgradeKey(null); // Set DB connection info diff --git a/lib/SP/Services/Upgrade/UpgradeAppService.php b/lib/SP/Services/Upgrade/UpgradeAppService.php index 6fdc43b2..e323437e 100644 --- a/lib/SP/Services/Upgrade/UpgradeAppService.php +++ b/lib/SP/Services/Upgrade/UpgradeAppService.php @@ -28,7 +28,7 @@ use SP\Config\ConfigData; use SP\Core\Events\Event; use SP\Core\Events\EventMessage; use SP\Services\Service; -use SP\Util\Util; +use SP\Util\Version; /** * Class UpgradeAppService @@ -46,7 +46,7 @@ final class UpgradeAppService extends Service implements UpgradeInterface */ public static function needsUpgrade($version) { - return Util::checkVersion($version, self::UPGRADES); + return Version::checkVersion($version, self::UPGRADES); } /** @@ -63,7 +63,7 @@ final class UpgradeAppService extends Service implements UpgradeInterface ); foreach (self::UPGRADES as $appVersion) { - if (Util::checkVersion($version, $appVersion)) { + if (Version::checkVersion($version, $appVersion)) { if ($this->applyUpgrade($appVersion) === false) { throw new UpgradeException( __u('Error al aplicar la actualización de la aplicación'), diff --git a/lib/SP/Services/Upgrade/UpgradeConfigService.php b/lib/SP/Services/Upgrade/UpgradeConfigService.php index 43c322d6..dd8bfaa7 100644 --- a/lib/SP/Services/Upgrade/UpgradeConfigService.php +++ b/lib/SP/Services/Upgrade/UpgradeConfigService.php @@ -28,7 +28,7 @@ use SP\Config\ConfigData; use SP\Core\Events\Event; use SP\Core\Events\EventMessage; use SP\Services\Service; -use SP\Util\Util; +use SP\Util\Version; /** * Class UpgradeService @@ -53,7 +53,7 @@ final class UpgradeConfigService extends Service implements UpgradeInterface */ public static function needsUpgrade($version) { - return Util::checkVersion(Util::checkVersion($version, Util::getVersionArrayNormalized()), self::UPGRADES); + return Version::checkVersion(Version::checkVersion($version, Version::getVersionArrayNormalized()), self::UPGRADES); } /** @@ -203,7 +203,7 @@ final class UpgradeConfigService extends Service implements UpgradeInterface $this->eventDispatcher->notifyEvent('upgrade.config.start', new Event($this, $message)); foreach (self::UPGRADES as $upgradeVersion) { - if (Util::checkVersion($version, $upgradeVersion)) { + if (Version::checkVersion($version, $upgradeVersion)) { $this->applyUpgrade($upgradeVersion); } } diff --git a/lib/SP/Services/Upgrade/UpgradeDatabaseService.php b/lib/SP/Services/Upgrade/UpgradeDatabaseService.php index b8cbe215..e267c59b 100644 --- a/lib/SP/Services/Upgrade/UpgradeDatabaseService.php +++ b/lib/SP/Services/Upgrade/UpgradeDatabaseService.php @@ -33,7 +33,7 @@ use SP\Storage\Database\MySQLFileParser; use SP\Storage\Database\QueryData; use SP\Storage\File\FileException; use SP\Storage\File\FileHandler; -use SP\Util\Util; +use SP\Util\Version; /** * Class UpgradeDatabaseService @@ -61,7 +61,7 @@ final class UpgradeDatabaseService extends Service implements UpgradeInterface */ public static function needsUpgrade($version) { - return empty($version) || Util::checkVersion($version, self::UPGRADES); + return empty($version) || Version::checkVersion($version, self::UPGRADES); } /** @@ -81,7 +81,7 @@ final class UpgradeDatabaseService extends Service implements UpgradeInterface ); foreach (self::UPGRADES as $upgradeVersion) { - if (Util::checkVersion($version, $upgradeVersion)) { + if (Version::checkVersion($version, $upgradeVersion)) { if ($this->applyPreUpgrade($upgradeVersion) === false) { throw new UpgradeException( __u('Error al aplicar la actualización auxiliar'), diff --git a/lib/SP/Services/Upgrade/UpgradeUtil.php b/lib/SP/Services/Upgrade/UpgradeUtil.php index 6a1ca3a1..70caa80c 100644 --- a/lib/SP/Services/Upgrade/UpgradeUtil.php +++ b/lib/SP/Services/Upgrade/UpgradeUtil.php @@ -26,6 +26,7 @@ namespace SP\Services\Upgrade; use SP\Config\Config; use SP\Util\Util; +use SP\Util\Version; /** * Class UpgradeUtil @@ -62,12 +63,12 @@ final class UpgradeUtil */ public function checkDbVersion() { - $appVersion = Util::getVersionStringNormalized(); + $appVersion = Version::getVersionStringNormalized(); $databaseVersion = UserUpgrade::fixVersionNumber(ConfigDB::getValue('version')); - if (Util::checkVersion($databaseVersion, $appVersion) + if (Version::checkVersion($databaseVersion, $appVersion) && Request::analyze('nodbupgrade', 0) === 0 - && Util::checkVersion($databaseVersion, self::$dbUpgrade) + && Version::checkVersion($databaseVersion, self::$dbUpgrade) && !$this->configData->isMaintenance() ) { $this->setUpgradeKey('db'); @@ -111,7 +112,7 @@ final class UpgradeUtil { $appVersion = UserUpgrade::fixVersionNumber($this->configData->getConfigVersion()); - if (Util::checkVersion($appVersion, self::$appUpgrade) && !$this->configData->isMaintenance()) { + if (Version::checkVersion($appVersion, self::$appUpgrade) && !$this->configData->isMaintenance()) { $this->setUpgradeKey('app'); // FIXME: send link for upgrading diff --git a/lib/SP/Util/Checks.php b/lib/SP/Util/Checks.php index 83171d4e..b4566b4b 100644 --- a/lib/SP/Util/Checks.php +++ b/lib/SP/Util/Checks.php @@ -31,28 +31,6 @@ namespace SP\Util; */ final class Checks { - /** - * Comprobar si la función de números aleatorios está disponible. - * - * @return bool - */ - public static function secureRNGIsAvailable() - { - // Check openssl_random_pseudo_bytes - if (function_exists('openssl_random_pseudo_bytes')) { - openssl_random_pseudo_bytes(1, $strong); - - if ($strong === true) { - return true; - } - } - - // Check /dev/urandom - $fp = @file_get_contents('/dev/urandom', false, null, 0, 1); - - return $fp !== false; - } - /** * Comprobar si sysPass se ejecuta en W$indows. * @@ -60,7 +38,7 @@ final class Checks */ public static function checkIsWindows() { - return 0 === strpos(PHP_OS, 'WIN'); + return strpos(PHP_OS, 'WIN') === 0; } /** @@ -72,59 +50,4 @@ final class Checks { return version_compare(PHP_VERSION, '7.0', '>=') && version_compare(PHP_VERSION, '7.3', '<'); } - - /** - * Comprobar los módulos necesarios. - * - * @return array con los módulos no disponibles - */ - public static function checkModules() - { - $modsNeed = [ - 'ldap', - 'curl', - 'SimpleXML', - 'Phar', - 'json', - 'xml', - 'PDO', - 'zlib', - 'gettext', - 'openssl', - 'pcre', - 'session', - 'gd', - 'mbstring' - ]; - - $missing = []; - - foreach ($modsNeed as $module) { - if (!extension_loaded($module)) { - $missing[] = $module; - } - } - - return $missing; - } - - /** - * Comprobar si el módulo CURL está instalado. - * - * @return bool - */ - public static function curlIsAvailable() - { - return extension_loaded('curl'); - } - - /** - * Comprobar si el módulo GD está instalado. - * - * @return bool - */ - public static function gdIsAvailable() - { - return extension_loaded('gd'); - } } diff --git a/lib/SP/Util/Connection.php b/lib/SP/Util/Connection.php index 82da7217..2f491edd 100644 --- a/lib/SP/Util/Connection.php +++ b/lib/SP/Util/Connection.php @@ -101,6 +101,39 @@ final class Connection implements ConnectionInterface return $this->socket; } + /** + * Obtener un socket del tipo TCP + * + * @return resource + */ + private function getTCPSocket() + { + return stream_socket_client('tcp://' . $this->host . ':' . $this->port, $this->errorno, $this->errorstr, self::SOCKET_TIMEOUT); +// return @socket_create(AF_INET, SOCK_STREAM, SOL_TCP); + } + + /** + * Obtener un socket del tipo UDP + * + * @return resource + */ + private function getUDPSocket() + { + return stream_socket_client('udp://' . $this->host . ':' . $this->port, $this->errorno, $this->errorstr, self::SOCKET_TIMEOUT); +// return @socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); + } + + /** + * Obtener el último error del socket + * + * @return string + */ + public function getSocketError() + { + return sprintf('%s (%d)', $this->errorstr, $this->errorno); +// return socket_strerror(socket_last_error($this->_socket)); + } + /** * Cerrar el socket */ @@ -121,49 +154,16 @@ final class Connection implements ConnectionInterface public function send($message) { if (!is_resource($this->socket)) { - throw new SPException(__('Socket no inicializado', false), SPException::WARNING); + throw new SPException(__u('Socket no inicializado'), SPException::WARNING); } $nBytes = @fwrite($this->socket, $message); // $nBytes = @socket_sendto($this->_socket, $message, strlen($message), 0, $this->_host, $this->port); if ($nBytes === false) { - throw new SPException(__('Error al enviar datos', false), SPException::WARNING, $this->getSocketError()); + throw new SPException(__u('Error al enviar datos'), SPException::WARNING, $this->getSocketError()); } return $nBytes; } - - /** - * Obtener el último error del socket - * - * @return string - */ - public function getSocketError() - { - return sprintf('%s (%d)', $this->errorstr, $this->errorno); -// return socket_strerror(socket_last_error($this->_socket)); - } - - /** - * Obtener un socket del tipo UDP - * - * @return resource - */ - private function getUDPSocket() - { - return stream_socket_client('udp://' . $this->host . ':' . $this->port, $this->errorno, $this->errorstr, self::SOCKET_TIMEOUT); -// return @socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); - } - - /** - * Obtener un socket del tipo TCP - * - * @return resource - */ - private function getTCPSocket() - { - return stream_socket_client('tcp://' . $this->host . ':' . $this->port, $this->errorno, $this->errorstr, self::SOCKET_TIMEOUT); -// return @socket_create(AF_INET, SOCK_STREAM, SOL_TCP); - } } \ No newline at end of file diff --git a/lib/SP/Util/ImageUtil.php b/lib/SP/Util/ImageUtil.php index 5a6b89ed..e9f42652 100644 --- a/lib/SP/Util/ImageUtil.php +++ b/lib/SP/Util/ImageUtil.php @@ -24,7 +24,9 @@ namespace SP\Util; +use SP\Core\Exceptions\CheckException; use SP\Core\Exceptions\InvalidImageException; +use SP\Core\PhpExtensionChecker; defined('APP_ROOT') || die(); @@ -35,6 +37,59 @@ defined('APP_ROOT') || die(); */ final class ImageUtil { + /** + * ImageUtil constructor. + * + * @param PhpExtensionChecker $checker + * + * @throws CheckException + */ + public function __construct(PhpExtensionChecker $checker) + { + $checker->checkCurlAvailable(true); + } + + /** + * Crear miniatura de una imagen + * + * @param $image string La imagen a redimensionar + * + * @return bool|string + * @throws InvalidImageException + */ + public function createThumbnail($image) + { + $im = @imagecreatefromstring($image); + + if ($im === false) { + throw new InvalidImageException(__u('Imagen no válida')); + } + + $width = imagesx($im); + $height = imagesy($im); + + // Calcular el tamaño de la miniatura + $new_width = 48; + $new_height = floor($height * ($new_width / $width)); + + // Crear nueva imagen + $imTmp = imagecreatetruecolor($new_width, $new_height); + + // Redimensionar la imagen + imagecopyresized($imTmp, $im, 0, 0, 0, 0, $new_width, $new_height, $width, $height); + + // Devolver la imagen + ob_start(); + imagepng($imTmp); + $thumbnail = ob_get_contents(); + ob_end_clean(); + + imagedestroy($imTmp); + imagedestroy($im); + + return base64_encode($thumbnail); + } + /** * Convertir un texto a imagen * @@ -42,14 +97,8 @@ final class ImageUtil * * @return bool|string */ - public static function convertText($text) + public function convertText($text) { - if (!Checks::gdIsAvailable()) { - logger(sprintf(__('Extensión \'%s\' no cargada'), 'GD')); - - return false; - } - $width = strlen($text) * 10; $im = @imagecreatetruecolor($width, 30); @@ -84,49 +133,4 @@ final class ImageUtil return base64_encode($image); } - - /** - * Crear miniatura de una imagen - * - * @param $image string La imagen a redimensionar - * - * @return bool|string - * @throws InvalidImageException - */ - public static function createThumbnail($image) - { - if (!Checks::gdIsAvailable()) { - logger(sprintf(__('Extensión \'%s\' no cargada'), 'GD')); - - return false; - } - - if (($im = @imagecreatefromstring($image)) === false) { - throw new InvalidImageException(__u('Imagen no válida')); - } - - $width = imagesx($im); - $height = imagesy($im); - - // Calcular el tamaño de la miniatura - $new_width = 48; - $new_height = floor($height * ($new_width / $width)); - - // Crear nueva imagen - $imTmp = imagecreatetruecolor($new_width, $new_height); - - // Redimensionar la imagen - imagecopyresized($imTmp, $im, 0, 0, 0, 0, $new_width, $new_height, $width, $height); - - // Devolver la imagen - ob_start(); - imagepng($imTmp); - $thumbnail = ob_get_contents(); - ob_end_clean(); - - imagedestroy($imTmp); - imagedestroy($im); - - return base64_encode($thumbnail); - } } \ No newline at end of file diff --git a/lib/SP/Util/Util.php b/lib/SP/Util/Util.php index 55f33c5b..72263541 100644 --- a/lib/SP/Util/Util.php +++ b/lib/SP/Util/Util.php @@ -29,8 +29,7 @@ use Defuse\Crypto\Encoding; use SP\Bootstrap; use SP\Config\ConfigData; use SP\Core\Exceptions\SPException; -use SP\Html\Html; -use SP\Services\Install\Installer; +use SP\Core\PhpExtensionChecker; defined('APP_ROOT') || die(); @@ -126,36 +125,6 @@ final class Util return Encoding::binToHex(Core::secureRandom($length)); } - - /** - * Devuelve el valor de la variable enviada por un formulario. - * - * @param string $s con el nombre de la variable - * @param string $d con el valor por defecto - * - * @return string con el valor de la variable - */ - public static function init_var($s, $d = '') - { - $r = $d; - if (isset($_REQUEST[$s]) && !empty($_REQUEST[$s])) { - $r = Html::sanitize($_REQUEST[$s]); - } - - return $r; - } - - - /** - * Devuelve la versión de sysPass. - * - * @return string con la versión - */ - public static function getVersionString() - { - return '2.2-dev'; - } - /** * Obtener datos desde una URL usando CURL * @@ -167,20 +136,18 @@ final class Util * @return bool|string * @throws \Psr\Container\NotFoundExceptionInterface * @throws \Psr\Container\ContainerExceptionInterface - * @throws SPException * - * @todo Use Guzzle + * TODO: Use Guzzle + * + * @throws \SP\Core\Exceptions\CheckException + * @throws SPException */ public static function getDataFromUrl($url, array $data = null, $useCookie = false, $weak = false) { /** @var ConfigData $ConfigData */ $ConfigData = Bootstrap::getContainer()->get(ConfigData::class); - if (!Checks::curlIsAvailable()) { - logger(sprintf(__('Extensión \'%s\' no cargada'), 'CURL')); - - throw new SPException(sprintf(__('Extensión \'%s\' no cargada'), 'CURL')); - } + Bootstrap::getContainer()->get(PhpExtensionChecker::class)->checkCurlAvailable(true); $ch = curl_init($url); @@ -236,15 +203,7 @@ final class Util $httpStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($data === false || $httpStatus !== 200) { - $Log = new Log(); - $LogMessgae = $Log->getLogMessage(); - $LogMessgae->setAction(__FUNCTION__); - $LogMessgae->addDescription(curl_error($ch)); - $LogMessgae->addDetails(__('Respuesta', false), $httpStatus); - $Log->setLogLevel(Log::ERROR); - $Log->writeLog(); - - throw new SPException($LogMessgae->getDescription(), SPException::WARNING); + throw new SPException(curl_error($ch), SPException::WARNING); } return $data; @@ -323,106 +282,10 @@ final class Util return $appinfo; } - /** - * Devolver versión normalizada en cadena - * - * @return string - */ - public static function getVersionStringNormalized() - { - return implode('', Installer::VERSION) . '.' . Installer::BUILD; - } - - /** - * Comprobar si una versión necesita actualización - * - * @param string $currentVersion - * @param array|string $upgradeableVersion - * - * @return bool True si la versión es menor. - */ - public static function checkVersion($currentVersion, $upgradeableVersion) - { - if (is_array($upgradeableVersion)) { - $upgradeableVersion = $upgradeableVersion[count($upgradeableVersion) - 1]; - } - - $currentVersion = self::normalizeVersionForCompare($currentVersion); - $upgradeableVersion = self::normalizeVersionForCompare($upgradeableVersion); - - if (empty($currentVersion) || empty($upgradeableVersion)) { - return false; - } - - if (PHP_INT_SIZE > 4) { - return version_compare($currentVersion, $upgradeableVersion) === -1; - } - - list($currentVersion, $build) = explode('.', $currentVersion, 2); - list($upgradeVersion, $upgradeBuild) = explode('.', $upgradeableVersion, 2); - - $versionRes = (int)$currentVersion < (int)$upgradeVersion; - - return (($versionRes && (int)$upgradeBuild === 0) - || ($versionRes && (int)$build < (int)$upgradeBuild)); - } - - /** - * Devuelve una versión normalizada para poder ser comparada - * - * @param string $versionIn - * - * @return string - */ - private static function normalizeVersionForCompare($versionIn) - { - if (is_string($versionIn) && !empty($versionIn)) { - list($version, $build) = explode('.', $versionIn); - - $nomalizedVersion = 0; - - foreach (str_split($version) as $key => $value) { - $nomalizedVersion += (int)$value * (10 ** (3 - $key)); - } - - return $nomalizedVersion . '.' . $build; - } - - return ''; - } - - /** - * Devuelve la versión de sysPass. - * - * @param bool $retBuild devolver el número de compilación - * - * @return array con el número de versión - */ - public static function getVersionArray($retBuild = false) - { - $version = array_values(Installer::VERSION); - - if ($retBuild === true) { - $version[] = Installer::BUILD; - - return $version; - } - - return $version; - } - - /** - * Devolver versión normalizada en array - * - * @return array - */ - public static function getVersionArrayNormalized() - { - return [implode('', Installer::VERSION), Installer::BUILD]; - } - /** * Realiza el proceso de logout. + * + * FIXME */ public static function logout() { @@ -472,21 +335,6 @@ final class Util return ($in ? true : false); } - /** - * Recorrer un array y escapar los carácteres no válidos en Javascript. - * - * @param $array - * - * @return array - */ - public static function arrayJSEscape(&$array) - { - array_walk($array, function (&$value) { - $value = str_replace(['\'', '"'], '\\\'', $value); - }); - return $array; - } - /** * Cast an object to another class, keeping the properties, but changing the methods * @@ -548,30 +396,6 @@ final class Util return unserialize(preg_replace('/^O:\d+:"[^"]++"/', 'O:' . strlen($class) . ':"' . $class . '"', $object)); } - /** - * Devuelve la última función llamada tras un error - * - * @param string $function La función utilizada como base - * - * @return string - */ - public static function traceLastCall($function = null) - { - $backtrace = debug_backtrace(0); - - if (count($backtrace) === 1) { - return $backtrace[1]['function']; - } - - foreach ($backtrace as $index => $fn) { - if ($fn['function'] === $function) { - return $backtrace[$index + 1]['function']; - } - } - - return ''; - } - /** * Bloquear la aplicación * diff --git a/lib/SP/Util/Version.php b/lib/SP/Util/Version.php new file mode 100644 index 00000000..22bee5f0 --- /dev/null +++ b/lib/SP/Util/Version.php @@ -0,0 +1,134 @@ +. + */ + +namespace SP\Util; + +use SP\Services\Install\Installer; + +/** + * Class Version + * + * @package SP\Util + */ +class Version +{ + + /** + * Devolver versión normalizada en cadena + * + * @return string + */ + public static function getVersionStringNormalized() + { + return implode('', Installer::VERSION) . '.' . Installer::BUILD; + } + + /** + * Comprobar si una versión necesita actualización + * + * @param string $currentVersion + * @param array|string $upgradeableVersion + * + * @return bool True si la versión es menor. + */ + public static function checkVersion($currentVersion, $upgradeableVersion) + { + if (is_array($upgradeableVersion)) { + $upgradeableVersion = $upgradeableVersion[count($upgradeableVersion) - 1]; + } + + $currentVersion = self::normalizeVersionForCompare($currentVersion); + $upgradeableVersion = self::normalizeVersionForCompare($upgradeableVersion); + + if (empty($currentVersion) || empty($upgradeableVersion)) { + return false; + } + + if (PHP_INT_SIZE > 4) { + return version_compare($currentVersion, $upgradeableVersion) === -1; + } + + list($currentVersion, $build) = explode('.', $currentVersion, 2); + list($upgradeVersion, $upgradeBuild) = explode('.', $upgradeableVersion, 2); + + $versionRes = (int)$currentVersion < (int)$upgradeVersion; + + return (($versionRes && (int)$upgradeBuild === 0) + || ($versionRes && (int)$build < (int)$upgradeBuild)); + } + + /** + * Devuelve una versión normalizada para poder ser comparada + * + * @param string $versionIn + * + * @return string + */ + public static function normalizeVersionForCompare($versionIn) + { + if (is_string($versionIn) && !empty($versionIn)) { + list($version, $build) = explode('.', $versionIn); + + $nomalizedVersion = 0; + + foreach (str_split($version) as $key => $value) { + $nomalizedVersion += (int)$value * (10 ** (3 - $key)); + } + + return $nomalizedVersion . '.' . $build; + } + + return ''; + } + + /** + * Devuelve la versión de sysPass. + * + * @param bool $retBuild devolver el número de compilación + * + * @return array con el número de versión + */ + public static function getVersionArray($retBuild = false) + { + $version = array_values(Installer::VERSION); + + if ($retBuild === true) { + $version[] = Installer::BUILD; + + return $version; + } + + return $version; + } + + /** + * Devolver versión normalizada en array + * + * @return array + */ + public static function getVersionArrayNormalized() + { + return [implode('', Installer::VERSION), Installer::BUILD]; + } +} \ No newline at end of file diff --git a/public/js/app-main.js b/public/js/app-main.js index bf33fb36..f1c40367 100644 --- a/public/js/app-main.js +++ b/public/js/app-main.js @@ -794,24 +794,26 @@ sysPass.Main = function () { opts.data = {isAjax: 1}; return appRequests.getActionCall(opts, function (json) { - // config.APP_ROOT = json.app_root; - config.LANG = json.lang; - config.PK = json.pk; - config.CHECK_UPDATES = json.check_updates; - config.CHECK_NOTICES = json.check_notices; - config.CRYPT.setPublicKey(json.pk); - config.TIMEZONE = json.timezone; - config.LOCALE = json.locale; - config.DEBUG = json.debug; - config.MAX_FILE_SIZE = parseInt(json.max_file_size); - config.COOKIES_ENABLED = json.cookies_enabled; - config.PLUGINS = json.plugins; - config.LOGGEDIN = json.loggedin; - config.AUTHBASIC_AUTOLOGIN = json.authbasic_autologin; - config.IMPORT_ALLOWED_EXTS = json.import_allowed_exts; - config.FILES_ALLOWED_EXTS = json.files_allowed_exts; + if (json.data !== undefined) { + // config.APP_ROOT = json.app_root; + config.LANG = json.data.lang; + config.PK = json.data.pk; + config.CHECK_UPDATES = json.data.check_updates; + config.CHECK_NOTICES = json.data.check_notices; + config.CRYPT.setPublicKey(json.data.pk); + config.TIMEZONE = json.data.timezone; + config.LOCALE = json.data.locale; + config.DEBUG = json.data.debug; + config.MAX_FILE_SIZE = parseInt(json.data.max_file_size); + config.COOKIES_ENABLED = json.data.cookies_enabled; + config.PLUGINS = json.data.plugins; + config.LOGGEDIN = json.data.loggedin; + config.AUTHBASIC_AUTOLOGIN = json.data.authbasic_autologin; + config.IMPORT_ALLOWED_EXTS = json.data.import_allowed_exts; + config.FILES_ALLOWED_EXTS = json.data.files_allowed_exts; - Object.freeze(config); + Object.freeze(config); + } }); }; diff --git a/public/js/app-main.min.js b/public/js/app-main.min.js index 5474b4fd..47c5d22e 100644 --- a/public/js/app-main.min.js +++ b/public/js/app-main.min.js @@ -19,8 +19,9 @@ LOCALE:"",DEBUG:"",COOKIES_ENABLED:!1,PLUGINS:[],LOGGEDIN:!1,AUTHBASIC_AUTOLOGIN Object.freeze(f);toastr.options={closeButton:!0,debug:!1,newestOnTop:!1,progressBar:!1,positionClass:"toast-top-center",preventDuplicates:!1,onclick:null,showDuration:"300",hideDuration:"1000",timeOut:"5000",extendedTimeOut:"1000",showEasing:"swing",hideEasing:"linear",showMethod:"fadeIn",hideMethod:"fadeOut"};var L=function(){f.info("setupCallbacks");var a=$("#container"),b=a.data("page");if(""!==b&&"function"===typeof q.views[b])q.views[b](a);0<$("footer").length&&q.views.footer();$("#btnBack").click(function(){A("index.php")}); q.bodyHooks()},d={ok:function(a){toastr.success(a)},error:function(a){toastr.error(a)},warn:function(a){toastr.warning(a)},info:function(a){toastr.info(a)},sticky:function(a,b){var e={timeOut:0};"function"===typeof b&&(e.onHidden=b);toastr.warning(a,c.LANG[60],e)},out:function(a){if("object"===typeof a){var b=a.status,e=a.description;void 0!==a.messages&&0"+a.messages.join("
        "));switch(b){case 0:d.ok(e);break;case 1:d.error(e);break;case 2:d.warn(e);break;case 10:u.main.logout(); break;case 100:d.ok(e);d.sticky(e);break;case 101:d.error(e);d.sticky(e);break;case 102:d.warn(e);d.sticky(e);break;default:d.error(e)}}},html:{error:function(a){return'

        Oops...
        '+c.LANG[1]+"
        "+a+"

        "}}};Object.freeze(d);String.format||(String.format=function(a){var b=Array.prototype.slice.call(arguments,1);return a.replace(/{(\d+)}/g,function(a,c){return"undefined"!==typeof b[c]?b[c]:a})});var M=function(){f.info("getEnvironment");var a=window.location.pathname.split("/"); -c.APP_ROOT=window.location.protocol+"//"+window.location.host+function(){for(var b="",c=1;c<=a.length-2;c++)b+="/"+a[c];return b}();var b=v.getRequestOpts();b.url="/index.php?r=bootstrap/getEnvironment";b.method="get";b.useLoading=!1;b.data={isAjax:1};return v.getActionCall(b,function(a){c.LANG=a.lang;c.PK=a.pk;c.CHECK_UPDATES=a.check_updates;c.CHECK_NOTICES=a.check_notices;c.CRYPT.setPublicKey(a.pk);c.TIMEZONE=a.timezone;c.LOCALE=a.locale;c.DEBUG=a.debug;c.MAX_FILE_SIZE=parseInt(a.max_file_size); -c.COOKIES_ENABLED=a.cookies_enabled;c.PLUGINS=a.plugins;c.LOGGEDIN=a.loggedin;c.AUTHBASIC_AUTOLOGIN=a.authbasic_autologin;c.IMPORT_ALLOWED_EXTS=a.import_allowed_exts;c.FILES_ALLOWED_EXTS=a.files_allowed_exts;Object.freeze(c)})},H=function(a){for(var b=[],c,d=window.location.href.slice(window.location.href.indexOf("?")+1).split("&"),f=0;f"); -p.complexity.numbers&&(c+="1234567890");p.complexity.chars&&(c+="abcdefghijklmnopqrstuvwxyz",p.complexity.uppercase&&(c+="ABCDEFGHIJKLMNOPQRSTUVWXYZ"));for(;b++b||64a.length);return a.toLowerCase()};return function(){f.info("init"); -y={actions:function(){return u},triggers:function(){return q},theme:function(){return B},sk:x,msg:d,log:f,passwordData:p,outputResult:C,checkPassLevel:F,encryptFormValue:z,fileUpload:G,redirect:A,scrollUp:J,setContentSize:K,generateRandomPass:N,uniqueId:O};r=$.extend({log:f,config:function(){return c},appTheme:function(){return B},appActions:function(){return u},appTriggers:function(){return q},appRequests:function(){return v},appPlugins:function(){return t},evalAction:m,resizeImage:h},y);Object.freeze(y); -Object.freeze(r);q=sysPass.Triggers(r);u=sysPass.Actions(r);v=sysPass.Requests(r);"function"===typeof sysPass.Theme&&(B=sysPass.Theme(r));M().then(function(){if(!I()&&(""!==c.PK&&n(),!1===c.COOKIES_ENABLED&&d.sticky(c.LANG[64]),w(),L(),0");p.complexity.numbers&&(c+="1234567890");p.complexity.chars&&(c+="abcdefghijklmnopqrstuvwxyz",p.complexity.uppercase&&(c+="ABCDEFGHIJKLMNOPQRSTUVWXYZ"));for(;b++b||64a.length);return a.toLowerCase()};return function(){f.info("init");y={actions:function(){return u},triggers:function(){return q},theme:function(){return B},sk:x,msg:d,log:f,passwordData:p,outputResult:C,checkPassLevel:F,encryptFormValue:z,fileUpload:G,redirect:A,scrollUp:J,setContentSize:K,generateRandomPass:N,uniqueId:O};r=$.extend({log:f,config:function(){return c},appTheme:function(){return B},appActions:function(){return u}, +appTriggers:function(){return q},appRequests:function(){return v},appPlugins:function(){return t},evalAction:m,resizeImage:h},y);Object.freeze(y);Object.freeze(r);q=sysPass.Triggers(r);u=sysPass.Actions(r);v=sysPass.Requests(r);"function"===typeof sysPass.Theme&&(B=sysPass.Theme(r));M().then(function(){if(!I()&&(""!==c.PK&&n(),!1===c.COOKIES_ENABLED&&d.sticky(c.LANG[64]),w(),L(),0. + */ + +namespace SP\Tests\SP\Core\Crypt; + +use Faker\Factory; +use PHPUnit\Framework\TestCase; +use SP\Core\Crypt\Hash; +use SP\Util\Util; + +/** + * Class HashTest + * + * @package SP\Tests\SP\Core\Crypt + */ +class HashTest extends TestCase +{ + /** + * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException + */ + public function testHashKey() + { + for ($i = 2; $i <= 128; $i *= 2) { + $key = Util::generateRandomBytes($i); + $hash = Hash::hashKey($key); + + $this->assertNotEmpty($hash); + $this->assertTrue(Hash::checkHashKey($key, $hash)); + } + } + + /** + * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException + */ + public function testSignMessage() + { + $faker = Factory::create(); + + for ($i = 2; $i <= 128; $i *= 2) { + $text = $faker->text; + + $key = Util::generateRandomBytes($i); + $hash = Hash::signMessage($text, $key); + + $this->assertNotEmpty($hash); + $this->assertTrue(Hash::checkMessage($text, $key, $hash)); + } + } +} diff --git a/test/SP/Core/Crypt/SecureKeyCookieTest.php b/test/SP/Core/Crypt/SecureKeyCookieTest.php new file mode 100644 index 00000000..ab512182 --- /dev/null +++ b/test/SP/Core/Crypt/SecureKeyCookieTest.php @@ -0,0 +1,99 @@ +. + */ + +namespace SP\Tests\SP\Core\Crypt; + +use Defuse\Crypto\Key; +use Faker\Factory; +use PHPUnit\Framework\TestCase; +use SP\Core\Crypt\SecureKeyCookie; +use SP\Http\Request; + +/** + * Class SecureKeyCookieTest + * + * @package SP\Tests\SP\Core\Crypt + */ +class SecureKeyCookieTest extends TestCase +{ + const FAKE_PROVIDERS = ['text', 'email', 'password', 'url', 'ipv4', 'ipv6', 'creditCardDetails']; + + /** + * @var SecureKeyCookie + */ + protected $cookie; + + public function testGetCookieData() + { + $faker = Factory::create(); + + $_SERVER['HTTP_USER_AGENT'] = $faker->userAgent; + + $cypher = $this->cookie->getCypher(); + + foreach (self::FAKE_PROVIDERS as $provider) { + $text = $faker->$provider; + + if (!is_scalar($text)) { + $text = serialize($text); + } + + $data = $this->cookie->sign($text, $cypher); + + $this->assertNotEmpty($data); + $this->assertContains(';', $data); + $this->assertEquals($text, $this->cookie->getCookieData($data, $cypher)); + } + } + + /** + * @throws \Defuse\Crypto\Exception\CryptoException + * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException + */ + public function testGetKey() + { + $_COOKIE[SecureKeyCookie::COOKIE_NAME] = $this->cookie->sign($this->cookie->generateSecuredData()->getSerialized(), $this->cookie->getCypher()); + + $this->assertNotEmpty($this->cookie->getKey()); + $this->assertInstanceOf(Key::class, $this->cookie->getSecuredKey()); + } + + /** + * testSaveKey + */ + public function testSaveKey() + { + $this->assertTrue($this->cookie->saveKey()); + $this->assertInstanceOf(Key::class, $this->cookie->getSecuredKey()); + } + + /** + * Sets up the fixture, for example, open a network connection. + * This method is called before a test is executed. + */ + protected function setUp() + { + $this->cookie = SecureKeyCookie::factory(new Request(\Klein\Request::createFromGlobals())); + } +} diff --git a/test/res/config/config.xml b/test/res/config/config.xml index 14716f72..1072a2be 100644 --- a/test/res/config/config.xml +++ b/test/res/config/config.xml @@ -9,11 +9,11 @@ 1 1 - 194b7b980763ac87cecdc1b9408494319faf7ed5 + 3daec30b56eb1f0e506d0ddc30b22aabb855f54d 0 0 - 1533163223 - d5ab2e6e9ba9c408bd7bacabef6d0dae1145e191 + 1533498924 + 7a5107e8d9487288f65b271640f2e2337c024a02 @@ -32,7 +32,7 @@ 0 - 9e1497c1a9f5085aa5512e6fa2e0089d4a967ca9 + e76e0b5f66010f0f5b81834c600d3dc025b099a6 PDF JPG
    - +