commercetools-8.x-1.2-alpha1/modules/commercetools_decoupled/src/EventSubscriber/CommercetoolsDecoupledGraphQlProxyAccessSubscriber.php
modules/commercetools_decoupled/src/EventSubscriber/CommercetoolsDecoupledGraphQlProxyAccessSubscriber.php
<?php
declare(strict_types=1);
namespace Drupal\commercetools_decoupled\EventSubscriber;
use Drupal\commercetools\Event\CommercetoolsGraphQlOperationEvent;
use Drupal\commercetools\Event\CommercetoolsGraphQlOperationResultEvent;
use Drupal\commercetools\Exception\CommercetoolsGraphqlAccessException;
use Drupal\Core\Session\AccountInterface;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\Parser;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Limit allowed GraphQL requests from the proxy page.
*/
class CommercetoolsDecoupledGraphQlProxyAccessSubscriber implements EventSubscriberInterface {
/**
* Array of parsed query objects.
*
* @var array
*/
protected array $queryObjects;
/**
* CommercetoolsDecoupledGraphQlProxyAccessSubscriber constructor.
*
* @param \Drupal\Core\Session\AccountInterface $currentUser
* The current user.
*/
public function __construct(
protected readonly AccountInterface $currentUser,
) {
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
return [
CommercetoolsGraphQlOperationEvent::class => ['onExecute', 300],
CommercetoolsGraphQlOperationResultEvent::class => ['onResult', 300],
];
}
/**
* Before GraphQl execution event handler.
*
* @throws \GraphQL\Error\SyntaxError
*/
public function onExecute(CommercetoolsGraphQlOperationEvent $event): void {
// Works only for proxy requests.
if (empty($event->variables['proxy'])) {
return;
}
$query = $event->query;
$variables = $event->variables;
$allowedOperation = $this->getAllowedOperations();
$queryObject = $this->getQueryObject($query);
$queryRootNodes = $queryObject->toArray()['definitions'];
for ($i = 0; $i < $queryRootNodes->count(); $i++) {
$node = $queryRootNodes->offsetGet($i);
// Getting the operation name.
$operation = $node->selectionSet->selections->offsetGet(0)->name->value;
if (!isset($allowedOperation[$operation])) {
$event->operationAllowed = FALSE;
break;
}
foreach ($allowedOperation[$operation] as $parameterName => $parameterValue) {
if (!isset($variables[$parameterName]) || !in_array($variables[$parameterName], $parameterValue)) {
$event->operationAllowed = FALSE;
break;
}
}
}
}
/**
* After GraphQl execution event handler.
*
* @throws \GraphQL\Error\SyntaxError
*/
public function onResult(CommercetoolsGraphQlOperationResultEvent $event): void {
// Works only for proxy requests.
if (empty($event->variables['proxy'])) {
return;
}
$query = $event->query;
$result = $event->result;
$queryObject = $this->getQueryObject($query);
$queryRootNodes = $queryObject->toArray()['definitions'];
for ($i = 0; $i < $queryRootNodes->count(); $i++) {
$node = $queryRootNodes->offsetGet($i);
// Getting the operation name.
$operation = $node->selectionSet->selections->offsetGet(0)->name->value;
$this->operationAccessCheckByResult($operation, $result);
}
}
/**
* Perform post-response access checks for a GraphQL operation.
*/
protected function operationAccessCheckByResult(string $operation, array $result): void {
switch ($operation) {
case 'orders':
if ($this->currentUser->isAnonymous()) {
$orders = $result['orders']['results'];
foreach ($orders as $order) {
if (!empty($order['customerId'])) {
throw new CommercetoolsGraphqlAccessException();
}
}
}
break;
}
}
/**
* Get parsed query object.
*
* @param string $query
* A GraphQL query.
*
* @return \GraphQL\Language\AST\DocumentNode
* Parsed GraphQL object.
*
* @throws \GraphQL\Error\SyntaxError
*/
protected function getQueryObject(string $query): DocumentNode {
// @todo Find a more lightweight library to parse the query.
$this->queryObjects[$query] ??= Parser::parse($query);
return $this->queryObjects[$query];
}
/**
* Return array of allowed operation with required parameters.
*/
protected function getAllowedOperations(): array {
return [
'productProjectionSearch' => [],
'products' => [],
'cart' => [
'id' => ['[current_cart:id]'],
],
'updateCart' => [
'id' => ['[current_cart_or_create:id]', '[current_cart:id]'],
],
'createOrderFromCart' => [],
'orders' => [],
'categories' => [],
'customer' => [],
'updateCustomer' => [],
];
}
}
