Current File : /var/www/vinorea/modules/ps_accounts/src/Http/Controller/AbstractRestController.php |
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License version 3.0
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
*/
namespace PrestaShop\Module\PsAccounts\Http\Controller;
use Context;
use ModuleFrontController;
use PrestaShop\Module\PsAccounts\Http\Exception\HttpException;
use PrestaShop\Module\PsAccounts\Http\Exception\MethodNotAllowedException;
use PrestaShop\Module\PsAccounts\Http\Exception\UnauthorizedException;
use PrestaShop\Module\PsAccounts\Polyfill\Traits\Controller\AjaxRender;
use PrestaShop\Module\PsAccounts\Provider\RsaKeysProvider;
use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository;
use PrestaShop\Module\PsAccounts\Service\SentryService;
use PrestaShop\Module\PsAccounts\Vendor\Lcobucci\JWT\Parser;
use PrestaShop\Module\PsAccounts\Vendor\Lcobucci\JWT\Signer\Hmac\Sha256;
use PrestaShop\Module\PsAccounts\Vendor\Lcobucci\JWT\Signer\Key;
use ReflectionException;
use ReflectionParameter;
abstract class AbstractRestController extends ModuleFrontController
{
use AjaxRender;
const TOKEN_HEADER = 'X-PrestaShop-Signature';
/**
* @var string
*/
public $resourceId = 'id';
/**
* @var \Ps_accounts
*/
public $module;
/**
* @var bool
*/
protected $authenticated = true;
public function __construct()
{
parent::__construct();
$this->ajax = true;
$this->content_only = true;
$this->controller_type = 'module';
}
/**
* @return void
*/
public function initContent()
{
}
/**
* @return void
*
* @throws \PrestaShopException
*/
// public function init()
// public function displayAjax()
public function postProcess()
{
try {
$payload = $this->extractPayload();
$method = $_SERVER['REQUEST_METHOD'];
// detect method from payload (hack with some shop server configuration)
if (isset($payload['method'])) {
$method = $payload['method'];
unset($payload['method']);
}
$this->dispatchVerb($method, $payload);
} catch (HttpException $e) {
$this->module->getLogger()->error($e);
$this->dieWithResponseJson([
'error' => true,
'message' => $e->getMessage(),
], $e->getStatusCode());
} catch (\Throwable $e) {
$this->handleError($e);
/* @phpstan-ignore-next-line */
} catch (\Exception $e) {
$this->handleError($e);
}
}
/**
* @param array $response
* @param int|null $httpResponseCode
*
* @return void
*
* @throws \PrestaShopException
*/
public function dieWithResponseJson(array $response, $httpResponseCode = null)
{
ob_end_clean();
if (is_integer($httpResponseCode)) {
http_response_code($httpResponseCode);
}
header('Content-Type: text/json');
$this->ajaxRender((string) json_encode($response));
}
/**
* @param string $httpMethod
* @param array $payload
*
* @return void
*
* @throws \Exception
*/
protected function dispatchVerb($httpMethod, array $payload)
{
$id = array_key_exists($this->resourceId, $payload)
? $payload[$this->resourceId]
: null;
$statusCode = 200;
switch ($httpMethod) {
case 'GET':
$method = null !== $id
? RestMethod::SHOW
: RestMethod::INDEX;
break;
case 'POST':
list($method, $statusCode) = null !== $id
? [RestMethod::UPDATE, $statusCode]
: [RestMethod::STORE, 201];
break;
case 'PUT':
case 'PATCH':
$method = RestMethod::UPDATE;
break;
case 'DELETE':
$statusCode = 204;
$method = RestMethod::DELETE;
break;
default:
throw new \Exception('Invalid Method : ' . $httpMethod);
}
$this->dieWithResponseJson($this->invokeMethod($method, $id, $payload), $statusCode);
}
/**
* @param string $method
* @param mixed $id
* @param mixed $payload
*
* @return mixed
*/
protected function invokeMethod($method, $id, $payload)
{
try {
$method = new \ReflectionMethod($this, $method);
$params = $method->getParameters();
$args = [];
if (null !== $id) {
$args[] = $this->buildResource($id);
}
if (null !== $payload) {
$args[] = $this->buildArg($payload, $params[1]);
}
return $method->invokeArgs($this, $args);
} catch (ReflectionException $e) {
throw new MethodNotAllowedException();
}
}
/**
* @param mixed $id
*
* @return mixed
*/
protected function buildResource($id)
{
return $id;
}
/**
* @param array $payload
* @param ReflectionParameter $reflectionParam
*
* @return array|object
*
* @throws ReflectionException
*/
protected function buildArg(array $payload, ReflectionParameter $reflectionParam)
{
// if ($reflectionParam->getType()->isBuiltin()) {
// return $payload;
// } else {
// // Instantiate DTO like value bag
// return $reflectionParam->getClass()->newInstance($payload);
// }
if ($reflectionParam->getClass()) {
// Instantiate DTO like value bag
return $reflectionParam->getClass()->newInstance($payload);
}
return $payload;
}
/**
* @return array
*/
protected function extractPayload()
{
$defaultShopId = Context::getContext()->shop->id;
if ($this->authenticated) {
return $this->decodePayload($defaultShopId);
}
return $this->decodeRawPayload($defaultShopId);
}
/**
* @param int $defaultShopId
*
* @return array
*/
protected function decodeRawPayload($defaultShopId = null)
{
$payload = $_REQUEST;
if (!isset($payload['shop_id'])) {
// context fallback
$payload['shop_id'] = $defaultShopId;
}
$shop = new \Shop((int) $payload['shop_id']);
if ($shop->id) {
$this->setContextShop($shop);
}
return $payload;
}
/**
* @param int $defaultShopId
*
* @return array
*/
protected function decodePayload($defaultShopId = null)
{
/** @var RsaKeysProvider $shopKeysService */
$shopKeysService = $this->module->getService(RsaKeysProvider::class);
$jwtString = $this->getRequestHeader(self::TOKEN_HEADER);
if ($jwtString) {
$jwt = (new Parser())->parse($jwtString);
$shop = new \Shop((int) $jwt->claims()->get('shop_id', $defaultShopId));
if ($shop->id) {
$this->setContextShop($shop);
$publicKey = $shopKeysService->getPublicKey();
$this->module->getLogger()->debug('trying to verify token with pkey: ' . $publicKey);
if (
null !== $publicKey &&
true === $jwt->verify(new Sha256(), new Key((string) $publicKey))
) {
$this->module->getLogger()->debug('token verified: ' . $jwtString);
return $jwt->claims()->all();
}
$this->module->getLogger()->error('Failed to verify token: ' . $jwtString);
}
$this->module->getLogger()->error('Failed to decode payload: ' . $jwtString);
}
throw new UnauthorizedException();
}
/**
* @param string $header
*
* @return string|null
*/
protected function getRequestHeader($header)
{
$headerValue = null;
$headerKey = 'HTTP_' . strtoupper(str_replace('-', '_', $header));
if (array_key_exists($headerKey, $_SERVER)) {
$headerValue = $_SERVER[$headerKey];
}
if (null === $headerValue) {
$headerValue = $this->getApacheHeader($header);
}
return $headerValue;
}
/**
* @param string $header
*
* @return string|null
*/
protected function getApacheHeader($header)
{
if (function_exists('apache_request_headers')) {
$headers = getallheaders();
//$header = preg_replace('/PrestaShop/', 'Prestashop', $header);
if (array_key_exists($header, $headers)) {
return $headers[$header];
}
}
return null;
}
/**
* Force shop context
*
* @param \Shop $shop
*
* @return void
*
* @throws \Exception
*/
protected function setContextShop(\Shop $shop)
{
/** @var ConfigurationRepository $conf */
$conf = $this->module->getService(ConfigurationRepository::class);
$conf->setShopId($shop->id);
/** @var Context $context */
$context = $this->module->getService('ps_accounts.context');
$context->shop = $shop;
}
/**
* @return bool
*/
protected function displayMaintenancePage()
{
return true;
}
/**
* Override displayRestrictedCountryPage to prevent page country is not allowed
*
* @see FrontController::displayRestrictedCountryPage()
*
* @return void
*/
protected function displayRestrictedCountryPage()
{
}
/**
* Override geolocationManagement to prevent country GEOIP blocking
*
* @see FrontController::geolocationManagement()
*
* @param \Country $defaultCountry
*
* @return false
*/
protected function geolocationManagement($defaultCountry)
{
return false;
}
/**
* @param \Throwable|\Exception $e
*
* @return void
*
* @throws \PrestaShopException
*/
protected function handleError($e)
{
SentryService::capture($e);
$this->dieWithResponseJson([
'error' => true,
'message' => 'Failed processing your request',
], 500);
}
}