AWS::buildDerivedKey()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 11
nc 1
nop 1
dl 0
loc 18
rs 9.9
c 1
b 0
f 0
1
<?php
2
3
/**
4
 * Part of the AmazonGiftCode package.
5
 * Author: Kashyap Merai <[email protected]>
6
 *
7
 */
8
9
10
namespace kamerk22\AmazonGiftCode\AWS;
11
12
13
use kamerk22\AmazonGiftCode\Client\Client;
14
use kamerk22\AmazonGiftCode\Config\Config;
15
use kamerk22\AmazonGiftCode\Exceptions\AmazonErrors;
16
use kamerk22\AmazonGiftCode\Response\CancelResponse;
17
use kamerk22\AmazonGiftCode\Response\CreateBalanceResponse;
18
use kamerk22\AmazonGiftCode\Response\CreateResponse;
19
20
class AWS
21
{
22
    public const SERVICE_NAME = 'AGCODService';
23
    public const ACCEPT_HEADER = 'accept';
24
    public const CONTENT_HEADER = 'content-type';
25
    public const HOST_HEADER = 'host';
26
    public const X_AMZ_DATE_HEADER = 'x-amz-date';
27
    public const X_AMZ_TARGET_HEADER = 'x-amz-target';
28
    public const AUTHORIZATION_HEADER = 'Authorization';
29
    public const AWS_SHA256_ALGORITHM = 'AWS4-HMAC-SHA256';
30
    public const KEY_QUALIFIER = 'AWS4';
31
    public const TERMINATION_STRING = 'aws4_request';
32
    public const CREATE_GIFT_CARD_SERVICE = 'CreateGiftCard';
33
    public const CANCEL_GIFT_CARD_SERVICE = 'CancelGiftCard';
34
    public const GET_AVAILABLE_FUNDS_SERVICE = 'GetAvailableFunds';
35
36
    private $_config;
37
38
39
    /**
40
     * AWS constructor.
41
     * @param Config $config
42
     */
43
    public function __construct(Config $config)
44
    {
45
        $this->_config = $config;
46
    }
47
48
49
    /**
50
     * @param $amount
51
     * @param $creationId
52
     * @return CreateResponse
53
     *
54
     * @throws AmazonErrors
55
     */
56
    public function getCode($amount, $creationId = null): CreateResponse
57
    {
58
        $serviceOperation = self::CREATE_GIFT_CARD_SERVICE;
59
        $payload = $this->getGiftCardPayload($amount, $creationId);
60
        $canonicalRequest = $this->getCanonicalRequest($serviceOperation, $payload);
61
        $dateTimeString = $this->getTimestamp();
62
        $result = json_decode($this->makeRequest($payload, $canonicalRequest, $serviceOperation, $dateTimeString), true);
63
        return new CreateResponse($result);
64
65
    }
66
67
    /**
68
     * @param $creationRequestId
69
     * @param $gcId
70
     * @return CancelResponse
71
     */
72
    public function cancelCode($creationRequestId, $gcId): CancelResponse
73
    {
74
        $serviceOperation = self::CANCEL_GIFT_CARD_SERVICE;
75
        $payload = $this->getCancelGiftCardPayload($creationRequestId, $gcId);
76
        $canonicalRequest = $this->getCanonicalRequest($serviceOperation, $payload);
77
        $dateTimeString = $this->getTimestamp();
78
        $result = json_decode($this->makeRequest($payload, $canonicalRequest, $serviceOperation, $dateTimeString), true);
79
        return new CancelResponse($result);
80
    }
81
82
    /**
83
     * @return CreateBalanceResponse
84
     */
85
    public function getBalance(): CreateBalanceResponse
86
    {
87
        $serviceOperation = self::GET_AVAILABLE_FUNDS_SERVICE;
88
        $payload = $this->getAvailableFundsPayload();
89
        $canonicalRequest = $this->getCanonicalRequest($serviceOperation, $payload);
90
        $dateTimeString = $this->getTimestamp();
91
        $result = json_decode($this->makeRequest($payload, $canonicalRequest, $serviceOperation, $dateTimeString), true);
92
        return new CreateBalanceResponse($result);
93
    }
94
95
    /**
96
     * @param $payload
97
     * @param $canonicalRequest
98
     * @param $serviceOperation
99
     * @param $dateTimeString
100
     * @return String
101
     */
102
    public function makeRequest($payload, $canonicalRequest, $serviceOperation, $dateTimeString): string
103
    {
104
        $KEY_QUALIFIER = self::KEY_QUALIFIER;
105
        $canonicalRequestHash = $this->buildHash($canonicalRequest);
106
        $stringToSign = $this->buildStringToSign($canonicalRequestHash);
107
        $authorizationValue = $this->buildAuthSignature($stringToSign);
108
109
        $secretKey = $this->_config->getSecret();
110
        $endpoint = $this->_config->getEndpoint();
111
        $regionName = $this->getRegion();
112
113
        $SERVICE_NAME = 'AGCODService';
114
        $serviceTarget = 'com.amazonaws.agcod.' . $SERVICE_NAME . '.' . $serviceOperation;
115
        $dateString = $this->getDateString();
116
117
        $signatureAWSKey = $KEY_QUALIFIER . $secretKey;
118
119
        $kDate = $this->hmac($dateString, $signatureAWSKey);
120
        $kDate_hexis = $this->hmac($dateString, $signatureAWSKey, false);
0 ignored issues
show
Unused Code introduced by
The assignment to $kDate_hexis is dead and can be removed.
Loading history...
121
        $kRegion = $this->hmac($regionName, $kDate);
122
        $kRegion_hexis = $this->hmac($regionName, $kDate, false);
0 ignored issues
show
Unused Code introduced by
The assignment to $kRegion_hexis is dead and can be removed.
Loading history...
123
        $kService_hexis = $this->hmac($SERVICE_NAME, $kRegion, false);
0 ignored issues
show
Unused Code introduced by
The assignment to $kService_hexis is dead and can be removed.
Loading history...
124
125
        $url = 'https://' . $endpoint . '/' . $serviceOperation;
126
        $headers = $this->buildHeaders($payload, $authorizationValue, $dateTimeString, $serviceTarget);
127
        return (new Client())->request($url, $headers, $payload);
128
    }
129
130
    /**
131
     * @param $payload
132
     * @param $authorizationValue
133
     * @param $dateTimeString
134
     * @param $serviceTarget
135
     * @return array
136
     */
137
    public function buildHeaders($payload, $authorizationValue, $dateTimeString, $serviceTarget): array
138
    {
139
        $ACCEPT_HEADER = self::ACCEPT_HEADER;
140
        $X_AMZ_DATE_HEADER = self::X_AMZ_DATE_HEADER;
141
        $X_AMZ_TARGET_HEADER = self::X_AMZ_TARGET_HEADER;
142
        $AUTHORIZATION_HEADER = self::AUTHORIZATION_HEADER;
143
        return [
144
            'Content-Type:' . $this->getContentType(),
145
            'Content-Length: ' . strlen($payload),
146
            $AUTHORIZATION_HEADER . ':' . $authorizationValue,
147
            $X_AMZ_DATE_HEADER . ':' . $dateTimeString,
148
            $X_AMZ_TARGET_HEADER . ':' . $serviceTarget,
149
            $ACCEPT_HEADER . ':' . $this->getContentType()
150
        ];
151
    }
152
153
    /**
154
     * @param $stringToSign
155
     * @return string
156
     */
157
    public function buildAuthSignature($stringToSign): string
158
    {
159
        $AWS_SHA256_ALGORITHM = self::AWS_SHA256_ALGORITHM;
160
        $SERVICE_NAME = self::SERVICE_NAME;
161
        $TERMINATION_STRING = self::TERMINATION_STRING;
162
        $ACCEPT_HEADER = self::ACCEPT_HEADER;
163
        $HOST_HEADER = self::HOST_HEADER;
164
        $X_AMZ_DATE_HEADER = self::X_AMZ_DATE_HEADER;
165
        $X_AMZ_TARGET_HEADER = self::X_AMZ_TARGET_HEADER;
166
167
        $awsKeyId = $this->_config->getAccessKey();
168
        $regionName = $this->getRegion();
169
170
        $dateString = $this->getDateString();
171
        $derivedKey = $this->buildDerivedKey();
172
        // Calculate signature per http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
173
        $finalSignature = $this->hmac($stringToSign, $derivedKey, false);
174
175
        // Assemble Authorization Header with signing information
176
        // per http://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html
177
        $authorizationValue =
178
            $AWS_SHA256_ALGORITHM
179
            . ' Credential=' . $awsKeyId
180
            . '/' . $dateString . '/' . $regionName . '/' . $SERVICE_NAME . '/' . $TERMINATION_STRING . ','
181
            . ' SignedHeaders='
182
            . $ACCEPT_HEADER . ';' . $HOST_HEADER . ';' . $X_AMZ_DATE_HEADER . ';' . $X_AMZ_TARGET_HEADER . ','
183
            . ' Signature='
184
            . $finalSignature;
185
186
        return $authorizationValue;
187
    }
188
189
    /**
190
     * @param $canonicalRequestHash
191
     * @return string
192
     */
193
    public function buildStringToSign($canonicalRequestHash): string
194
    {
195
        $AWS_SHA256_ALGORITHM = self::AWS_SHA256_ALGORITHM;
196
        $TERMINATION_STRING = self::TERMINATION_STRING;
197
        $SERVICE_NAME = self::SERVICE_NAME;
198
        $regionName = $this->getRegion();
199
        $dateTimeString = $this->getTimestamp();
200
        $dateString = $this->getDateString();
201
        $stringToSign = "$AWS_SHA256_ALGORITHM\n$dateTimeString\n$dateString/$regionName/$SERVICE_NAME/$TERMINATION_STRING\n$canonicalRequestHash";
202
203
        return $stringToSign;
204
    }
205
206
    /**
207
     * @param bool $rawOutput
208
     * @return string
209
     */
210
    public function buildDerivedKey($rawOutput = true): string
211
    {
212
        $KEY_QUALIFIER = self::KEY_QUALIFIER;
213
        $TERMINATION_STRING = self::TERMINATION_STRING;
214
        $SERVICE_NAME = self::SERVICE_NAME;
215
216
        $awsSecretKey = $this->_config->getSecret();
217
        // Append Key Qualifier, "AWS4", to secret key per http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html
218
        $signatureAWSKey = $KEY_QUALIFIER . $awsSecretKey;
219
        $regionName = $this->getRegion();
220
        $dateString = $this->getDateString();
221
222
        $kDate = $this->hmac($dateString, $signatureAWSKey);
223
        $kRegion = $this->hmac($regionName, $kDate);
224
        $kService = $this->hmac($SERVICE_NAME, $kRegion);
225
226
        // Derived the Signing key (derivedKey aka kSigning)
227
        return $this->hmac($TERMINATION_STRING, $kService, $rawOutput);
228
    }
229
230
    /**
231
     * @return string
232
     */
233
    public function getRegion(): string
234
    {
235
        $endpoint = $this->_config->getEndpoint();
236
        $regionName = 'us-east-1';
237
238
        if ($endpoint === 'agcod-v2-eu.amazon.com' || $endpoint === 'agcod-v2-eu-gamma.amazon.com') {
239
            $regionName = 'eu-west-1';
240
        } else if ($endpoint === 'agcod-v2-fe.amazon.com' || $endpoint === 'agcod-v2-fe-gamma.amazon.com') {
241
            $regionName = 'us-west-2';
242
        }
243
        return $regionName;
244
    }
245
246
247
    /**
248
     * @param $amount
249
     * @param $creationId
250
     * @return string
251
     */
252
    public function getGiftCardPayload($amount, $creationId = null): string
253
    {
254
        $amount = trim($amount);
255
        $payload = [
256
            'creationRequestId' => $creationId ?: uniqid($this->_config->getPartner().'_'),
257
            'partnerId' => $this->_config->getPartner(),
258
            'value' =>
259
                [
260
                    'currencyCode' => $this->_config->getCurrency(),
261
                    'amount' => (float)$amount
262
                ]
263
        ];
264
        return json_encode($payload);
265
    }
266
267
    /**
268
     * @param $creationRequestId
269
     * @param $gcId
270
     * @return string
271
     */
272
    public function getCancelGiftCardPayload($creationRequestId, $gcId): string
273
    {
274
        $gcResponseId = trim($gcId);
275
        $payload = [
276
            'creationRequestId' => $creationRequestId,
277
            'partnerId' => $this->_config->getPartner(),
278
            'gcId' => $gcResponseId
279
        ];
280
        return json_encode($payload);
281
    }
282
283
    /**
284
     * @return string
285
     */
286
    public function getAvailableFundsPayload(): string
287
    {
288
        $payload = [
289
            'partnerId' => $this->_config->getPartner(),
290
        ];
291
        return json_encode($payload);
292
    }
293
294
    /**
295
     * @param $serviceOperation
296
     * @param $payload
297
     * @return string
298
     */
299
    public function getCanonicalRequest($serviceOperation, $payload): string
300
    {
301
        $HOST_HEADER = self::HOST_HEADER;
302
        $X_AMZ_DATE_HEADER = self::X_AMZ_DATE_HEADER;
303
        $X_AMZ_TARGET_HEADER = self::X_AMZ_TARGET_HEADER;
304
        $ACCEPT_HEADER = self::ACCEPT_HEADER;
305
        $payloadHash = $this->buildHash($payload);
306
        $canonicalHeaders = $this->buildCanonicalHeaders($serviceOperation);
307
        $canonicalRequest = "POST\n/$serviceOperation\n\n$canonicalHeaders\n\n$ACCEPT_HEADER;$HOST_HEADER;$X_AMZ_DATE_HEADER;$X_AMZ_TARGET_HEADER\n$payloadHash";
308
        return $canonicalRequest;
309
    }
310
311
    /**
312
     * @param $data
313
     * @return string
314
     */
315
    public function buildHash($data): string
316
    {
317
        return hash('sha256', $data);
318
    }
319
320
    /**
321
     * @return false|string
322
     */
323
    public function getTimestamp()
324
    {
325
        return gmdate('Ymd\THis\Z');
326
    }
327
328
    /**
329
     * @param $data
330
     * @param $key
331
     * @param bool $raw
332
     * @return string
333
     */
334
    public function hmac($data, $key, $raw = true): string
335
    {
336
        return hash_hmac('sha256', $data, $key, $raw);
337
    }
338
339
    /**
340
     * @return bool|string
341
     */
342
    public function getDateString()
343
    {
344
        return substr($this->getTimestamp(), 0, 8);
345
    }
346
347
    /**
348
     * @return string
349
     */
350
    public function getContentType(): string
351
    {
352
        return 'application/json';
353
    }
354
355
    /**
356
     * @param $serviceOperation
357
     * @return string
358
     */
359
    public function buildCanonicalHeaders($serviceOperation): string
360
    {
361
        $ACCEPT_HEADER = self::ACCEPT_HEADER;
362
        $HOST_HEADER = self::HOST_HEADER;
363
        $X_AMZ_DATE_HEADER = self::X_AMZ_DATE_HEADER;
364
        $X_AMZ_TARGET_HEADER = self::X_AMZ_TARGET_HEADER;
365
        $dateTimeString = $this->getTimestamp();
366
        $endpoint = $this->_config->getEndpoint();
367
        $contentType = $this->getContentType();
368
        return
369
            "$ACCEPT_HEADER:$contentType\n$HOST_HEADER:$endpoint\n$X_AMZ_DATE_HEADER:$dateTimeString\n$X_AMZ_TARGET_HEADER:com.amazonaws.agcod.AGCODService.$serviceOperation";
370
    }
371
}
372