Passed
Pull Request — master (#7)
by
unknown
10:39 queued 07:49
created

AWS::buildDerivedKey()   A

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
     * @return CreateResponse
52
     *
53
     * @throws AmazonErrors
54
     */
55
    public function getCode($amount): CreateResponse
56
    {
57
        $serviceOperation = self::CREATE_GIFT_CARD_SERVICE;
58
        $payload = $this->getGiftCardPayload($amount);
59
        $canonicalRequest = $this->getCanonicalRequest($serviceOperation, $payload);
60
        $dateTimeString = $this->getTimestamp();
61
        $result = json_decode($this->makeRequest($payload, $canonicalRequest, $serviceOperation, $dateTimeString), true);
62
        return new CreateResponse($result);
63
64
    }
65
66
    /**
67
     * @param $creationRequestId
68
     * @param $gcId
69
     * @return CancelResponse
70
     */
71
    public function cancelCode($creationRequestId, $gcId): CancelResponse
72
    {
73
        $serviceOperation = self::CANCEL_GIFT_CARD_SERVICE;
74
        $payload = $this->getCancelGiftCardPayload($creationRequestId, $gcId);
75
        $canonicalRequest = $this->getCanonicalRequest($serviceOperation, $payload);
76
        $dateTimeString = $this->getTimestamp();
77
        $result = json_decode($this->makeRequest($payload, $canonicalRequest, $serviceOperation, $dateTimeString), true);
78
        return new CancelResponse($result);
79
    }
80
81
    /**
82
     * @return CreateBalanceResponse
83
     */
84
    public function getBalance(): CreateBalanceResponse
85
    {
86
        $serviceOperation = self::GET_AVAILABLE_FUNDS_SERVICE;
87
        $payload = $this->getAvailableFundsPayload();
88
        $canonicalRequest = $this->getCanonicalRequest($serviceOperation, $payload);
89
        $dateTimeString = $this->getTimestamp();
90
        $result = json_decode($this->makeRequest($payload, $canonicalRequest, $serviceOperation, $dateTimeString), true);
91
        return new CreateBalanceResponse($result);
92
    }
93
94
    /**
95
     * @param $payload
96
     * @param $canonicalRequest
97
     * @param $serviceOperation
98
     * @param $dateTimeString
99
     * @return String
100
     */
101
    public function makeRequest($payload, $canonicalRequest, $serviceOperation, $dateTimeString): string
102
    {
103
        $KEY_QUALIFIER = self::KEY_QUALIFIER;
104
        $canonicalRequestHash = $this->buildHash($canonicalRequest);
105
        $stringToSign = $this->buildStringToSign($canonicalRequestHash);
106
        $authorizationValue = $this->buildAuthSignature($stringToSign);
107
108
        $secretKey = $this->_config->getSecret();
109
        $endpoint = $this->_config->getEndpoint();
110
        $regionName = $this->getRegion();
111
112
        $SERVICE_NAME = 'AGCODService';
113
        $serviceTarget = 'com.amazonaws.agcod.' . $SERVICE_NAME . '.' . $serviceOperation;
114
        $dateString = $this->getDateString();
115
116
        $signatureAWSKey = $KEY_QUALIFIER . $secretKey;
117
118
        $kDate = $this->hmac($dateString, $signatureAWSKey);
119
        $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...
120
        $kRegion = $this->hmac($regionName, $kDate);
121
        $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...
122
        $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...
123
124
        $url = 'https://' . $endpoint . '/' . $serviceOperation;
125
        $headers = $this->buildHeaders($payload, $authorizationValue, $dateTimeString, $serviceTarget);
126
        return (new Client())->request($url, $headers, $payload);
127
    }
128
129
    /**
130
     * @param $payload
131
     * @param $authorizationValue
132
     * @param $dateTimeString
133
     * @param $serviceTarget
134
     * @return array
135
     */
136
    public function buildHeaders($payload, $authorizationValue, $dateTimeString, $serviceTarget): array
137
    {
138
        $ACCEPT_HEADER = self::ACCEPT_HEADER;
139
        $X_AMZ_DATE_HEADER = self::X_AMZ_DATE_HEADER;
140
        $X_AMZ_TARGET_HEADER = self::X_AMZ_TARGET_HEADER;
141
        $AUTHORIZATION_HEADER = self::AUTHORIZATION_HEADER;
142
        return [
143
            'Content-Type:' . $this->getContentType(),
144
            'Content-Length: ' . strlen($payload),
145
            $AUTHORIZATION_HEADER . ':' . $authorizationValue,
146
            $X_AMZ_DATE_HEADER . ':' . $dateTimeString,
147
            $X_AMZ_TARGET_HEADER . ':' . $serviceTarget,
148
            $ACCEPT_HEADER . ':' . $this->getContentType()
149
        ];
150
    }
151
152
    /**
153
     * @param $stringToSign
154
     * @return string
155
     */
156
    public function buildAuthSignature($stringToSign): string
157
    {
158
        $AWS_SHA256_ALGORITHM = self::AWS_SHA256_ALGORITHM;
159
        $SERVICE_NAME = self::SERVICE_NAME;
160
        $TERMINATION_STRING = self::TERMINATION_STRING;
161
        $ACCEPT_HEADER = self::ACCEPT_HEADER;
162
        $HOST_HEADER = self::HOST_HEADER;
163
        $X_AMZ_DATE_HEADER = self::X_AMZ_DATE_HEADER;
164
        $X_AMZ_TARGET_HEADER = self::X_AMZ_TARGET_HEADER;
165
166
        $awsKeyId = $this->_config->getAccessKey();
167
        $regionName = $this->getRegion();
168
169
        $dateString = $this->getDateString();
170
        $derivedKey = $this->buildDerivedKey();
171
        // Calculate signature per http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
172
        $finalSignature = $this->hmac($stringToSign, $derivedKey, false);
173
174
        // Assemble Authorization Header with signing information
175
        // per http://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html
176
        $authorizationValue =
177
            $AWS_SHA256_ALGORITHM
178
            . ' Credential=' . $awsKeyId
179
            . '/' . $dateString . '/' . $regionName . '/' . $SERVICE_NAME . '/' . $TERMINATION_STRING . ','
180
            . ' SignedHeaders='
181
            . $ACCEPT_HEADER . ';' . $HOST_HEADER . ';' . $X_AMZ_DATE_HEADER . ';' . $X_AMZ_TARGET_HEADER . ','
182
            . ' Signature='
183
            . $finalSignature;
184
185
        return $authorizationValue;
186
    }
187
188
    /**
189
     * @param $canonicalRequestHash
190
     * @return string
191
     */
192
    public function buildStringToSign($canonicalRequestHash): string
193
    {
194
        $AWS_SHA256_ALGORITHM = self::AWS_SHA256_ALGORITHM;
195
        $TERMINATION_STRING = self::TERMINATION_STRING;
196
        $SERVICE_NAME = self::SERVICE_NAME;
197
        $regionName = $this->getRegion();
198
        $dateTimeString = $this->getTimestamp();
199
        $dateString = $this->getDateString();
200
        $stringToSign = "$AWS_SHA256_ALGORITHM\n$dateTimeString\n$dateString/$regionName/$SERVICE_NAME/$TERMINATION_STRING\n$canonicalRequestHash";
201
202
        return $stringToSign;
203
    }
204
205
    /**
206
     * @param bool $rawOutput
207
     * @return string
208
     */
209
    public function buildDerivedKey($rawOutput = true): string
210
    {
211
        $KEY_QUALIFIER = self::KEY_QUALIFIER;
212
        $TERMINATION_STRING = self::TERMINATION_STRING;
213
        $SERVICE_NAME = self::SERVICE_NAME;
214
215
        $awsSecretKey = $this->_config->getSecret();
216
        // Append Key Qualifier, "AWS4", to secret key per http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html
217
        $signatureAWSKey = $KEY_QUALIFIER . $awsSecretKey;
218
        $regionName = $this->getRegion();
219
        $dateString = $this->getDateString();
220
221
        $kDate = $this->hmac($dateString, $signatureAWSKey);
222
        $kRegion = $this->hmac($regionName, $kDate);
223
        $kService = $this->hmac($SERVICE_NAME, $kRegion);
224
225
        // Derived the Signing key (derivedKey aka kSigning)
226
        return $this->hmac($TERMINATION_STRING, $kService, $rawOutput);
227
    }
228
229
    /**
230
     * @return string
231
     */
232
    public function getRegion(): string
233
    {
234
235
236
        $endpoint = $this->_config->getEndpoint();
237
        $regionName = 'us-east-1';
238
239
        if ($endpoint === 'agcod-v2-eu.amazon.com' || $endpoint === 'agcod-v2-eu-gamma.amazon.com') {
240
            $regionName = 'eu-west-1';
241
        } else if ($endpoint === 'agcod-v2-fe.amazon.com' || $endpoint === 'agcod-v2-fe-gamma.amazon.com') {
242
            $regionName = 'us-west-2';
243
        }
244
        return $regionName;
245
    }
246
247
248
    /**
249
     * @param $amount
250
     * @return string
251
     */
252
    public function getGiftCardPayload($amount, $creationId = null): string
0 ignored issues
show
Unused Code introduced by
The parameter $creationId is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

252
    public function getGiftCardPayload($amount, /** @scrutinizer ignore-unused */ $creationId = null): string

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
253
    {
254
        $amount = trim($amount);
255
        $payload = [
256
           "creationRequestId" => $this->_config->getPartner() . "_" . time(),
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