Skip to content

Commit caadeba

Browse files
author
viktor-zinchenko
authored
Add AWS IAM authorization support (mghoneimy#64)
1 parent a983697 commit caadeba

File tree

8 files changed

+202
-5
lines changed

8 files changed

+202
-5
lines changed

.github/workflows/php.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,7 @@ jobs:
3737

3838
- name: Run test suite
3939
run: composer run test
40+
env:
41+
AWS_ACCESS_KEY_ID: key
42+
AWS_SECRET_ACCESS_KEY: secret
43+
AWS_SESSION_TOKEN: token

composer.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@
3636
"guzzlehttp/guzzle": "^6.3|^7.0.1"
3737
},
3838
"require-dev": {
39-
"phpunit/phpunit": "^7.5|^8.0",
40-
"codacy/coverage": "^1.4"
39+
"phpunit/phpunit": "^7.5|^8.0|^9.0",
40+
"codacy/coverage": "^1.4",
41+
"aws/aws-sdk-php": "^3.186"
4142
},
4243
"conflict": {
4344
"guzzlehttp/psr7": "< 1.7.0"
@@ -46,6 +47,7 @@
4647
"test": "phpunit tests/ --whitelist src/ --coverage-clover build/coverage/xml"
4748
},
4849
"suggest": {
50+
"aws/aws-sdk-php": "Move this package to require section to use AWS IAM authorization",
4951
"gmostafa/php-graphql-oqm": "To have object-to-query mapping support"
5052
}
5153
}

src/Auth/AuthInterface.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace GraphQL\Auth;
4+
5+
use GuzzleHttp\Psr7\Request;
6+
7+
interface AuthInterface
8+
{
9+
/**
10+
* @param Request $request
11+
* @param array $options
12+
* @return Request
13+
*/
14+
public function run(Request $request, array $options = []): Request;
15+
}

src/Auth/AwsIamAuth.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
namespace GraphQL\Auth;
4+
5+
use Aws\Credentials\Credentials;
6+
use Aws\Credentials\CredentialProvider;
7+
use Aws\Signature\SignatureV4;
8+
use GraphQL\Exception\AwsRegionNotSetException;
9+
use GraphQL\Exception\MissingAwsSdkPackageException;
10+
use GuzzleHttp\Psr7\Request;
11+
12+
class AwsIamAuth implements AuthInterface
13+
{
14+
protected const SERVICE_NAME = 'appsync';
15+
16+
/**
17+
* @codeCoverageIgnore
18+
*
19+
* AwsIamAuth constructor.
20+
*/
21+
public function __construct()
22+
{
23+
if (!class_exists('\Aws\Signature\SignatureV4')) {
24+
throw new MissingAwsSdkPackageException();
25+
}
26+
}
27+
28+
/**
29+
* @param Request $request
30+
* @param array $options
31+
* @return Request
32+
*/
33+
public function run(Request $request, array $options = []): Request
34+
{
35+
$region = $options['aws_region'] ?? null;
36+
if ($region === null) {
37+
throw new AwsRegionNotSetException();
38+
}
39+
return $this->getSignature($region)->signRequest(
40+
$request, $this->getCredentials(),
41+
self::SERVICE_NAME
42+
);
43+
}
44+
45+
/**
46+
* @param string $region
47+
* @return SignatureV4
48+
*/
49+
protected function getSignature(string $region): SignatureV4
50+
{
51+
return new SignatureV4(self::SERVICE_NAME, $region);
52+
}
53+
54+
/**
55+
* @return Credentials
56+
*/
57+
protected function getCredentials(): Credentials
58+
{
59+
$provider = CredentialProvider::defaultProvider();
60+
return $provider()->wait();
61+
}
62+
}

src/Client.php

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace GraphQL;
44

5+
use GraphQL\Auth\AuthInterface;
6+
use GraphQL\Auth\HeaderAuth;
57
use GraphQL\Exception\QueryError;
68
use GraphQL\Exception\MethodNotSupportedException;
79
use GraphQL\QueryBuilder\QueryBuilderInterface;
@@ -34,39 +36,54 @@ class Client
3436
*/
3537
protected $httpHeaders;
3638

39+
/**
40+
* @var array
41+
*/
42+
protected $options;
43+
3744
/**
3845
* @var string
3946
*/
4047
protected $requestMethod;
4148

49+
/**
50+
* @var AuthInterface
51+
*/
52+
protected $auth;
53+
4254
/**
4355
* Client constructor.
4456
*
4557
* @param string $endpointUrl
4658
* @param array $authorizationHeaders
4759
* @param array $httpOptions
48-
* @param ClientInterface $httpClient
60+
* @param ClientInterface|null $httpClient
4961
* @param string $requestMethod
62+
* @param AuthInterface|null $auth
5063
*/
5164
public function __construct(
5265
string $endpointUrl,
5366
array $authorizationHeaders = [],
5467
array $httpOptions = [],
5568
ClientInterface $httpClient = null,
56-
string $requestMethod = 'POST'
69+
string $requestMethod = 'POST',
70+
AuthInterface $auth = null
5771
) {
5872
$headers = array_merge(
5973
$authorizationHeaders,
6074
$httpOptions['headers'] ?? [],
6175
['Content-Type' => 'application/json']
6276
);
63-
6477
/**
6578
* All headers will be set on the request objects explicitly,
6679
* Guzzle doesn't have to care about them at this point, so to avoid any conflicts
6780
* we are removing the headers from the options
6881
*/
6982
unset($httpOptions['headers']);
83+
$this->options = $httpOptions;
84+
if ($auth) {
85+
$this->auth = $auth;
86+
}
7087

7188
$this->endpointUrl = $endpointUrl;
7289
$this->httpClient = $httpClient ?? new GuzzleAdapter(new \GuzzleHttp\Client($httpOptions));
@@ -121,6 +138,10 @@ public function runRawQuery(string $queryString, $resultsAsArray = false, array
121138
$bodyArray = ['query' => (string) $queryString, 'variables' => $variables];
122139
$request = $request->withBody(Utils::streamFor(json_encode($bodyArray)));
123140

141+
if ($this->auth) {
142+
$request = $this->auth->run($request, $this->options);
143+
}
144+
124145
// Send api request and get response
125146
try {
126147
$response = $this->httpClient->sendRequest($request);
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace GraphQL\Exception;
4+
5+
use RunTimeException;
6+
7+
/**
8+
* Class AwsRegionNotSetException
9+
*
10+
* @package GraphQL\Exception
11+
*/
12+
class AwsRegionNotSetException extends RunTimeException
13+
{
14+
public function __construct()
15+
{
16+
parent::__construct("AWS region not set.");
17+
}
18+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace GraphQL\Exception;
4+
5+
use RunTimeException;
6+
7+
/**
8+
* Class MissingAwsSdkPackageException
9+
*
10+
* @package GraphQL\Exception
11+
*/
12+
class MissingAwsSdkPackageException extends RunTimeException
13+
{
14+
/**
15+
* @codeCoverageIgnore
16+
*
17+
* MissingAwsSdkPackageException constructor.
18+
*/
19+
public function __construct()
20+
{
21+
parent::__construct(
22+
'To be able to use AWS IAM authorization you should
23+
install "aws/aws-sdk-php" as a project dependency.'
24+
);
25+
}
26+
}

tests/Auth/AwsIamAuthTest.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
namespace GraphQL\Tests\Auth;
4+
5+
use GraphQL\Auth\AwsIamAuth;
6+
use GraphQL\Exception\AwsRegionNotSetException;
7+
use GuzzleHttp\Psr7\Request;
8+
use PHPUnit\Framework\TestCase;
9+
10+
class AwsIamAuthTest extends TestCase
11+
{
12+
/**
13+
* @var AwsIamAuth
14+
*/
15+
protected $auth;
16+
17+
protected function setUp(): void
18+
{
19+
$this->auth = new AwsIamAuth();
20+
}
21+
22+
/**
23+
* @covers \GraphQL\Auth\AwsIamAuth::run
24+
* @covers \GraphQL\Exception\AwsRegionNotSetException::__construct
25+
*/
26+
public function testRunMissingRegion()
27+
{
28+
$this->expectException(AwsRegionNotSetException::class);
29+
$request = new Request('POST', '');
30+
$this->auth->run($request, []);
31+
}
32+
33+
/**
34+
* @covers \GraphQL\Auth\AwsIamAuth::run
35+
* @covers \GraphQL\Auth\AwsIamAuth::getSignature
36+
* @covers \GraphQL\Auth\AwsIamAuth::getCredentials
37+
*/
38+
public function testRunSuccess()
39+
{
40+
$request = $this->auth->run(
41+
new Request('POST', ''),
42+
['aws_region' => 'us-east-1']
43+
);
44+
$headers = $request->getHeaders();
45+
$this->assertArrayHasKey('X-Amz-Date', $headers);
46+
$this->assertArrayHasKey('X-Amz-Security-Token', $headers);
47+
$this->assertArrayHasKey('Authorization', $headers);
48+
}
49+
}

0 commit comments

Comments
 (0)