first commit
This commit is contained in:
182
bundles/KupShop/GraphQLBundle/Controller/AdminController.php
Normal file
182
bundles/KupShop/GraphQLBundle/Controller/AdminController.php
Normal file
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace KupShop\GraphQLBundle\Controller;
|
||||
|
||||
use GraphQL\Executor\ExecutionResult;
|
||||
use GraphQL\GraphQL;
|
||||
use KupShop\AdminBundle\Util\LegacyAdminCredentials;
|
||||
use KupShop\GraphQLBundle\Util\SchemaFactory;
|
||||
use KupShop\KupShopBundle\Context\PosContext;
|
||||
use KupShop\KupShopBundle\Routing\AdminRoute;
|
||||
use KupShop\KupShopBundle\Util\Logging\SentryLogger;
|
||||
use KupShop\KupShopBundle\Util\RequestUtil;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use TheCodingMachine\GraphQLite\Context\Context;
|
||||
|
||||
class AdminController
|
||||
{
|
||||
public function __construct(
|
||||
private SchemaFactory $schemaFactory,
|
||||
private SentryLogger $sentryLogger,
|
||||
private LoggerInterface $logger,
|
||||
public PosContext $posContext,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @AdminRoute("/graphql{slash}", requirements={"slash": "\/$"}, defaults={"slash": "/"})
|
||||
*/
|
||||
public function graphQLAdmin(Request $request, LegacyAdminCredentials $legacyAdminCredentials): Response
|
||||
{
|
||||
if (!findModule(\Modules::GRAPHQL, \Modules::SUB_GRAPHQL_ADMIN)) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
increaseMaxExecutionTime(120);
|
||||
increaseMemoryLimit(512);
|
||||
|
||||
// CORS check - return 200
|
||||
if ($request->isMethod('OPTIONS')) {
|
||||
return $this->getAPIResponse([]);
|
||||
}
|
||||
|
||||
$token = $request->headers->get('X-Access-Token');
|
||||
|
||||
if (!$legacyAdminCredentials->isLogged() && (!$token || !$legacyAdminCredentials->loginByHash($token))) {
|
||||
$this->log('Unauthorized access', [
|
||||
'token' => $token,
|
||||
'uri' => $request->getUri(),
|
||||
'headers' => $request->headers->all(),
|
||||
]);
|
||||
|
||||
return $this->getAPIResponse(['errors' => ['message' => 'Unauthorized access']], 401);
|
||||
}
|
||||
|
||||
$legacyAdminCredentials->setAdminGlobalVars();
|
||||
|
||||
$admin = LegacyAdminCredentials::getAdminById($legacyAdminCredentials->getAdminID());
|
||||
|
||||
if (!findRight('API')) {
|
||||
return $this->getAPIResponse(['errors' => ['message' => 'Used access token does not have access to the API']], 403);
|
||||
}
|
||||
|
||||
$schema = $this->schemaFactory->createAdminSchema();
|
||||
|
||||
$input = json_decode($request->getContent(), true);
|
||||
|
||||
if (($query = ($input['query'] ?? null)) === null) {
|
||||
return $this->getAPIResponse(['errors' => ['message' => 'Invalid request! Missing "query" param']], 400);
|
||||
}
|
||||
|
||||
$variableValues = $input['variables'] ?? null;
|
||||
|
||||
$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);
|
||||
$output = $result->toArray();
|
||||
|
||||
$this->captureGraphQLRequest($input, $admin);
|
||||
$this->captureGraphQLErrors($result);
|
||||
|
||||
return $this->getAPIResponse($output);
|
||||
}
|
||||
|
||||
private function captureGraphQLRequest(array $input, ?array $admin = null): void
|
||||
{
|
||||
$this->log('API Request', [
|
||||
'input' => $input,
|
||||
'admin' => ['id' => $admin['id'], 'email' => $admin['email'], 'login' => $admin['login'], 'name' => $admin['name'] ?? ''],
|
||||
]);
|
||||
}
|
||||
|
||||
private function captureGraphQLErrors(ExecutionResult $result): void
|
||||
{
|
||||
foreach ($result->errors ?? [] as $error) {
|
||||
// pokud je chyba client safe, tak ji neloguju do sentry
|
||||
if ($error->isClientSafe()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isLocalDevelopment()) {
|
||||
throw $error;
|
||||
}
|
||||
|
||||
$this->sentryLogger->captureException($error);
|
||||
}
|
||||
}
|
||||
|
||||
private function log(string $message, array $data): void
|
||||
{
|
||||
if (isLocalDevelopment()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->logger->notice("[GraphQL] {$message}", $data);
|
||||
}
|
||||
|
||||
private function getAPIResponse(array $data, int $code = 200): JsonResponse
|
||||
{
|
||||
return new JsonResponse(
|
||||
$data,
|
||||
$code,
|
||||
[
|
||||
'Content-Type' => 'application/json',
|
||||
'Access-Control-Allow-Origin' => '*',
|
||||
'Access-Control-Allow-Headers' => '*',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @AdminRoute("/pos/graphql{slash}", requirements={"slash": "\/$"}, defaults={"slash": "/"})
|
||||
*/
|
||||
public function graphQLPos(Request $request, LegacyAdminCredentials $legacyAdminCredentials, RequestUtil $requestUtil): Response
|
||||
{
|
||||
if (!findModule(\Modules::NEW_POS)) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
// Checks if the request has a pos ID in the header and activates context
|
||||
if ($idPos = PosContext::getIdFromRequest($request)) {
|
||||
$this->posContext->activate($idPos);
|
||||
}
|
||||
|
||||
// Checks the API versions, and if they differ, returns 426 Upgrade Required.
|
||||
if ($this->posContext->getApiVersion() !== PosContext::getVersionFromRequest($request)) {
|
||||
return $this->getAPIResponse([], 426);
|
||||
}
|
||||
|
||||
// CORS check - return 200
|
||||
if ($request->isMethod('OPTIONS')) {
|
||||
return $this->getAPIResponse([]);
|
||||
}
|
||||
|
||||
$token = $request->headers->get('X-Access-Token');
|
||||
if (!$legacyAdminCredentials->isLogged() && (!$token || !$legacyAdminCredentials->loginByHash($token))) {
|
||||
return $this->getAPIResponse(['errors' => ['message' => 'Unauthorized access']], 401);
|
||||
}
|
||||
|
||||
$legacyAdminCredentials->setAdminGlobalVars();
|
||||
|
||||
if (!findRight('POS_API')) {
|
||||
return $this->getAPIResponse(['errors' => ['message' => 'User does not have access']], 403);
|
||||
}
|
||||
|
||||
$schema = $this->schemaFactory->createPosSchema();
|
||||
$input = json_decode($request->getContent(), true);
|
||||
if (!$input) {
|
||||
return $this->getAPIResponse(['errors' => ['message' => 'Unprocessable Entity - empty input']], 422);
|
||||
}
|
||||
|
||||
if (preg_match('/^\s*(query|mutation)\s*(\w+)/i', $input['query'], $matches)) {
|
||||
$requestUtil->addTransactionInfo($request, ['query' => $matches[2]]);
|
||||
}
|
||||
|
||||
$result = GraphQL::executeQuery($schema, $input['query'], null, new Context(), $input['variables'] ?? null);
|
||||
$output = $result->toArray();
|
||||
|
||||
return $this->getAPIResponse($output);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace KupShop\GraphQLBundle\Controller;
|
||||
|
||||
use GraphQL\Error\CoercionError;
|
||||
use GraphQL\Server\Exception\MissingContentTypeHeader;
|
||||
use KupShop\KupShopBundle\Util\RequestUtil;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\EventListener\AbstractSessionListener;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use TheCodingMachine\GraphQLite\Bundle\Controller\GraphQLiteController;
|
||||
|
||||
class FrontendController extends GraphQLiteController
|
||||
{
|
||||
#[Route(path: '/graphql', name: 'graphqliteRoute')]
|
||||
public function graphql(Request $request, RequestUtil $requestUtil): Response
|
||||
{
|
||||
$query = join(',', array_unique($this->getGraphQLActions($request)));
|
||||
$requestUtil->addTransactionInfo($request, ['action' => join(':', ['graphql', $query]), 'query' => $query]);
|
||||
|
||||
try {
|
||||
$response = $this->handleRequest($request);
|
||||
} catch (\RuntimeException|MissingContentTypeHeader|CoercionError $e) {
|
||||
// runtime exception is thrown from `handleRequest` when invalid data passed
|
||||
// or MissingContentTypeHeader when empty Content-Type header is set
|
||||
// CoercionError is caused by invalid Datetime in query variable
|
||||
// all of these should result to BadRequestHttpException
|
||||
throw new BadRequestHttpException($e->getMessage(), $e);
|
||||
}
|
||||
|
||||
if ($request->isMethod('GET') && !getAdminUser()) {
|
||||
$response->headers->set(AbstractSessionListener::NO_AUTO_CACHE_CONTROL_HEADER, 'true');
|
||||
$response->setMaxAge(0);
|
||||
$response->setPublic();
|
||||
$response->setSharedMaxAge(60 * 15);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function getGraphQLActions(Request $request): array
|
||||
{
|
||||
$actions = [];
|
||||
|
||||
$input = json_decode($request->getContent(), true) ?? null;
|
||||
|
||||
if (!is_array($input)) {
|
||||
return $actions;
|
||||
}
|
||||
|
||||
foreach (array_is_list($input) ? $input : [$input] as $operation) {
|
||||
$operationName = $operation['operationName'] ?? null;
|
||||
|
||||
if (!$operationName && preg_match('/^\s*(query|mutation)\s*(\w+)/i', $operation['query'] ?? '', $matches)) {
|
||||
$operationName = $matches[2] ?? null;
|
||||
}
|
||||
|
||||
if ($operationName) {
|
||||
$actions[] = $operationName;
|
||||
}
|
||||
}
|
||||
|
||||
return $actions;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user