CloudObjects / Directory / Amazon Web Services Integration / AWSClientProvider V4
Sign in

AWSClientProvider V4

a phpmae:Class in Amazon Web Services Integration

Provides a generic API client for Amazon Webservices which uses the signature version 4.

Public PHP Methods
  • signRequest(RequestInterface $request)

    No documentation available.

  • getClient()

    Get a Guzzle HTTP Client configured with AWS signature V4 authentication.

Source Code
<?php

use Psr\Http\Message\RequestInterface;
use GuzzleHttp\Handler\CurlHandler;
use GuzzleHttp\HandlerStack, GuzzleHttp\Client, GuzzleHttp\Middleware;
use CloudObjects\PhpMAE\ConfigLoader;

/**
 * Implementation for coid://aws.3rd-party.co/AWSClientProvider/V4
 */
class AWSClientProvider {

    private $awsAccessKeyId;
    private $awsSecretAccessKey;
    private $client;
    
    public function __construct(ConfigLoader $config) {
        $priorities = [ 'callerClass', 'callerClass.namespace' ];
        $this->awsAccessKeyId = $config->get('coid://aws.3rd-party.co/accessKeyId', $priorities);
        $this->awsSecretAccessKey = $config->get('coid://aws.3rd-party.co/secretAccessKey', $priorities);
    
        if (!isset($this->awsAccessKeyId) || !isset($this->awsSecretAccessKey))
          throw new \Exception("AWS credentials required.");
    }

    private function getService($segments) {
        if (((count($segments)==4 && $segments[2]=='amazonaws' && $segments[3]=='com')
            || (count($segments)==3 && $segments[1]=='amazonaws' && $segments[2]=='com'))
            && in_array($segments[0], [ 'appstream', 'cloudsearch', 'cloudtrail',
                'monitoring', 'logs', 'cognito-identity', 'cognito-sync', 'dynamodb',
                'ec2', 'elasticmapreduce', 'elastictranscoder', 'elasticache', 'glacier',
                'kinesis', 'mechanicalturk', 'mobileanalytics', 'redshift', 'rds',
                'route53', 'route53domains', 'email', 'sdb', 'sns', 'sqs', 'swf',
                'autoscaling', 'cloudformation', 'datapipeline', 'directconnect',
                'elasticbeanstalk', 'iam', 'importexport', 'opsworks', 'sts',
                'storagegateway', 'support', 'elasticloadbalancing' ])) {
            return $segments[0];
        } else
            return 's3';
    }

    private function getRegion($segments) {
        if (count($segments)==4 && $segments[2]=='amazonaws' && $segments[3]=='com'
            && in_array($segments[1], [ 'us-east-1', 'us-west-2', 'us-west-1',
                'eu-west-1', 'eu-central-1', 'ap-southeast-1', 'ap-southeast-2',
                'ap-northeast-1', 'sa-east-1' ])) {
            return $segments[1];
        } else
            return 'us-east-1';
    }

    public function signRequest(RequestInterface $request) {
        $date = new \DateTime('UTC');
		$request = $request->withHeader('x-amz-date', $date->format('Ymd\THis\Z'));

		if (get_class($request)=='GuzzleHttp\Psr7\Request') {
            $contentHash = hash('SHA256', $request->getBody());      
		} else {
			// pre-calculated hash for an empty string
			$contentHash = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';
		}

        // Parse query (if any)
        $queryMap = [];
        if (!empty($request->getUri()->getQuery())) {
            $query = explode('&', $request->getUri()->getQuery());
            foreach ($query as $q) {
                $kv = explode('=', $q);
                $queryMap[$kv[0]] = urlencode($kv[1]);
            }
            uksort($queryMap, 'strcmp');
        }        

		// Prepare canonical request
		$canonicalRequest = array(
			$request->getMethod(),
			str_replace('%2F','/', rawurlencode($request->getUri()->getPath())),
			http_build_query($queryMap)
        );

		foreach ($request->getHeaders() as $name => $values) {
			$headers[strtolower($name)] = trim((string)$values[0]);
		}
		uksort($headers, 'strcmp');
		foreach ($headers as $key => $value) {
			$canonicalRequest[] = $key.':'.$value;
		}

		$canonicalRequest[] = '';
		$canonicalRequest[] = implode(';', array_keys($headers));
        $canonicalRequest[] = $contentHash;

		// Prepare string for signing
		$segments = explode('.', $request->getUri()->getHost());
		$service = $this->getService($segments);
		$region = $this->getRegion($segments);

		$scope = $date->format('Ymd').'/'.$region.'/'.$service.'/aws4_request';
		$string = implode("\n", array(
			'AWS4-HMAC-SHA256',
			$date->format('Ymd\THis\Z'),
			$scope,
			hash('SHA256', implode("\n", $canonicalRequest))
		));

		// Create signature
		$kSecret = 'AWS4'.$this->awsSecretAccessKey;
		$kDate = hash_hmac('SHA256', $date->format('Ymd'), $kSecret, true);
		$kRegion = hash_hmac('SHA256', $region, $kDate, true);
		$kService = hash_hmac('SHA256', $service, $kRegion, true);
		$kSigning = hash_hmac('SHA256', 'aws4_request', $kService, true);

		$signature = hash_hmac('SHA256', $string, $kSigning);

		// Prepare authorization header
		$authorization = array(
			'Credential='.$this->awsAccessKeyId.'/'.$scope,
			'SignedHeaders='.implode(';', array_keys($headers)),
			'Signature='.$signature,
        );

        //die('AWS4-HMAC-SHA256'.' '.implode(',', $authorization));

		$request = $request->withHeader('x-amz-content-sha256', $contentHash)
            ->withHeader('Authorization', 'AWS4-HMAC-SHA256'.' '.implode(',', $authorization));		  
    
        return $request;
    }

    /**
     * Get a Guzzle HTTP Client configured with AWS signature V4 authentication.
     */
    public function getClient() {
        if (!isset($this->client)) {
            $stack = new HandlerStack();
            $stack->setHandler(new CurlHandler());
            $stack->push(Middleware::mapRequest([$this, 'signRequest']));
            $stack->push(Middleware::httpErrors());
            $this->client = new Client([ 'handler' => $stack ]);
        }

        return $this->client;
    }

}
Meta
URI / COID
coid://aws.3rd-party.co/AWSClientProvider/V4 content_copy
Revision
3-47ecd846795844fe3d87a3acfa38a420 content_copy
Short ID
aws:AWSClientProvider/V4 content_copy
Reference URL
https://coid.link/aws.3rd-party.co/AWSClientProvider/V4 content_copy
Last updated
2021-04-22 11:26 (UTC)
Created at
2021-04-22 11:26 (UTC)