Skip to content

Commit 8f401aa

Browse files
authored
LYNX-319 401 and 403 HTTP response codes for GraphQL API
1 parent 9afb10a commit 8f401aa

File tree

7 files changed

+357
-30
lines changed

7 files changed

+357
-30
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
/**
3+
* Copyright 2024 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CustomerGraphQl\Controller\HttpRequestValidator;
9+
10+
use Magento\Framework\App\HttpRequestInterface;
11+
use Magento\Framework\Exception\AuthorizationException;
12+
use Magento\Framework\GraphQl\Exception\GraphQlAuthenticationException;
13+
use Magento\GraphQl\Controller\HttpRequestValidatorInterface;
14+
use Magento\Integration\Api\Exception\UserTokenException;
15+
use Magento\Integration\Api\UserTokenReaderInterface;
16+
use Magento\Integration\Api\UserTokenValidatorInterface;
17+
18+
class AuthorizationRequestValidator implements HttpRequestValidatorInterface
19+
{
20+
private const AUTH = 'Authorization';
21+
private const BEARER = 'bearer';
22+
/**
23+
* AuthorizationRequestValidator Constructor
24+
*
25+
* @param UserTokenReaderInterface $tokenReader
26+
* @param UserTokenValidatorInterface $tokenValidator
27+
*/
28+
public function __construct(
29+
private readonly UserTokenReaderInterface $tokenReader,
30+
private readonly UserTokenValidatorInterface $tokenValidator
31+
) {
32+
}
33+
34+
/**
35+
* Validate the authorization header bearer token if it is set
36+
*
37+
* @param HttpRequestInterface $request
38+
* @return void
39+
* @throws GraphQlAuthenticationException
40+
*/
41+
public function validate(HttpRequestInterface $request): void
42+
{
43+
$authorizationHeaderValue = $request->getHeader(self::AUTH);
44+
if (!$authorizationHeaderValue) {
45+
return;
46+
}
47+
48+
$headerPieces = explode(' ', $authorizationHeaderValue);
49+
if (count($headerPieces) !== 2 || strtolower($headerPieces[0]) !== self::BEARER) {
50+
return;
51+
}
52+
53+
try {
54+
$this->tokenValidator->validate($this->tokenReader->read($headerPieces[1]));
55+
} catch (UserTokenException | AuthorizationException $exception) {
56+
throw new GraphQlAuthenticationException(__($exception->getMessage()));
57+
}
58+
}
59+
}

app/code/Magento/CustomerGraphQl/etc/graphql/di.xml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<?xml version="1.0"?>
22
<!--
33
/**
4-
* Copyright © Magento, Inc. All rights reserved.
5-
* See COPYING.txt for license details.
4+
* Copyright 2024 Adobe
5+
* All Rights Reserved.
66
*/
77
-->
88
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
@@ -212,4 +212,11 @@
212212
<plugin name="merge_order_after_customer_signup"
213213
type="Magento\CustomerGraphQl\Plugin\Model\MergeGuestOrder" />
214214
</type>
215+
<type name="Magento\GraphQl\Controller\HttpRequestProcessor">
216+
<arguments>
217+
<argument name="requestValidators" xsi:type="array">
218+
<item name="authorizationValidator" xsi:type="object">Magento\CustomerGraphQl\Controller\HttpRequestValidator\AuthorizationRequestValidator</item>
219+
</argument>
220+
</arguments>
221+
</type>
215222
</config>

app/code/Magento/GraphQl/Controller/GraphQl.php

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
<?php
2-
32
/**
4-
* Copyright © Magento, Inc. All rights reserved.
5-
* See COPYING.txt for license details.
3+
* Copyright 2024 Adobe
4+
* All Rights Reserved.
65
*/
7-
86
declare(strict_types=1);
97

108
namespace Magento\GraphQl\Controller;
119

10+
use Exception;
1211
use Magento\Framework\App\Area;
1312
use Magento\Framework\App\AreaList;
1413
use Magento\Framework\App\FrontControllerInterface;
@@ -24,6 +23,8 @@
2423
use Magento\Framework\GraphQl\Query\QueryProcessor;
2524
use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
2625
use Magento\Framework\GraphQl\Schema\SchemaGeneratorInterface;
26+
use Magento\Framework\GraphQl\Exception\GraphQlAuthenticationException;
27+
use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException;
2728
use Magento\Framework\Serialize\SerializerInterface;
2829
use Magento\Framework\Webapi\Response;
2930
use Magento\GraphQl\Helper\Query\Logger\LogData;
@@ -184,7 +185,7 @@ public function dispatch(RequestInterface $request): ResponseInterface
184185
$statusCode = 200;
185186
$jsonResult = $this->jsonFactory->create();
186187
$data = $this->getDataFromRequest($request);
187-
$result = [];
188+
$result = ['errors' => []];
188189

189190
$schema = null;
190191
$query = $data['query'] ?? '';
@@ -205,8 +206,14 @@ public function dispatch(RequestInterface $request): ResponseInterface
205206
$this->contextFactory->create(),
206207
$data['variables'] ?? []
207208
);
208-
} catch (\Exception $error) {
209-
$result['errors'] = isset($result['errors']) ? $result['errors'] : [];
209+
$statusCode = $this->getHttpResponseCode($result);
210+
} catch (GraphQlAuthenticationException $error) {
211+
$result['errors'][] = $this->graphQlError->create($error);
212+
$statusCode = 401;
213+
} catch (GraphQlAuthorizationException $error) {
214+
$result['errors'][] = $this->graphQlError->create($error);
215+
$statusCode = 403;
216+
} catch (Exception $error) {
210217
$result['errors'][] = $this->graphQlError->create($error);
211218
$statusCode = ExceptionFormatter::HTTP_GRAPH_QL_SCHEMA_ERROR_STATUS;
212219
}
@@ -216,7 +223,7 @@ public function dispatch(RequestInterface $request): ResponseInterface
216223
$jsonResult->renderResult($this->httpResponse);
217224

218225
// log information about the query, unless it is an introspection query
219-
if (strpos($query, 'IntrospectionQuery') === false) {
226+
if (!str_contains($query, 'IntrospectionQuery')) {
220227
$queryInformation = $this->logDataHelper->getLogData($request, $data, $schema, $this->httpResponse);
221228
$this->loggerPool->execute($queryInformation);
222229
}
@@ -247,4 +254,30 @@ private function getDataFromRequest(RequestInterface $request): array
247254

248255
return $data;
249256
}
257+
258+
/**
259+
* Retrieve http response code based on the error categories
260+
*
261+
* @param array $result
262+
* @return int
263+
*/
264+
private function getHttpResponseCode(array $result): int
265+
{
266+
if (empty($result['errors'])) {
267+
return 200;
268+
}
269+
foreach ($result['errors'] as $error) {
270+
if (!isset($error['extensions']['category'])) {
271+
continue;
272+
}
273+
switch ($error['extensions']['category']) {
274+
case GraphQlAuthenticationException::EXCEPTION_CATEGORY:
275+
return 401;
276+
case GraphQlAuthorizationException::EXCEPTION_CATEGORY:
277+
return 403;
278+
}
279+
}
280+
281+
return 200;
282+
}
250283
}

0 commit comments

Comments
 (0)