OAuth::getSortedParameters()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
cc 3
nc 3
nop 1
1
<?php
2
/**
3
 * WooCommerce oAuth1.0
4
 *
5
 * @category HttpClient
6
 * @package  Automattic/WooCommerce
7
 */
8
9
namespace Automattic\WooCommerce\HttpClient;
10
11
/**
12
 * oAuth1.0 class.
13
 *
14
 * @package Automattic/WooCommerce
15
 */
16
class OAuth
17
{
18
19
    /**
20
     * OAuth signature method algorithm.
21
     */
22
    const HASH_ALGORITHM = 'SHA256';
23
24
    /**
25
     * API endpoint URL.
26
     *
27
     * @var string
28
     */
29
    protected $url;
30
31
    /**
32
     * Consumer key.
33
     *
34
     * @var string
35
     */
36
    protected $consumerKey;
37
38
    /**
39
     * Consumer secret.
40
     *
41
     * @var string
42
     */
43
    protected $consumerSecret;
44
45
    /**
46
     * API version.
47
     *
48
     * @var string
49
     */
50
    protected $apiVersion;
51
52
    /**
53
     * Request method.
54
     *
55
     * @var string
56
     */
57
    protected $method;
58
59
    /**
60
     * Request parameters.
61
     *
62
     * @var array
63
     */
64
    protected $parameters;
65
66
    /**
67
     * Timestamp.
68
     *
69
     * @var string
70
     */
71
    protected $timestamp;
72
73
    /**
74
     * Initialize oAuth class.
75
     *
76
     * @param string $url            Store URL.
77
     * @param string $consumerKey    Consumer key.
78
     * @param string $consumerSecret Consumer Secret.
79
     * @param string $method         Request method.
80
     * @param string $apiVersion     API version.
81
     * @param array  $parameters     Request parameters.
82
     * @param string $timestamp      Timestamp.
83
     */
84
    public function __construct(
85
        $url,
86
        $consumerKey,
87
        $consumerSecret,
88
        $apiVersion,
89
        $method,
90
        $parameters = [],
91
        $timestamp = ''
92
    ) {
93
        $this->url            = $url;
94
        $this->consumerKey    = $consumerKey;
95
        $this->consumerSecret = $consumerSecret;
96
        $this->apiVersion     = $apiVersion;
97
        $this->method         = $method;
98
        $this->parameters     = $parameters;
99
        $this->timestamp      = $timestamp;
100
    }
101
102
    /**
103
     * Encode according to RFC 3986.
104
     *
105
     * @param string|array $value Value to be normalized.
106
     *
107
     * @return string
108
     */
109
    protected function encode($value)
110
    {
111
        if (is_array($value)) {
112
            return array_map([$this, 'encode'], $value);
113
        } else {
114
            return str_replace(['+', '%7E'], [' ', '~'], rawurlencode($value));
115
        }
116
    }
117
118
    /**
119
     * Normalize parameters.
120
     *
121
     * @param array $parameters Parameters to normalize.
122
     *
123
     * @return array
124
     */
125
    protected function normalizeParameters($parameters)
126
    {
127
        $normalized = [];
128
129
        foreach ($parameters as $key => $value) {
130
            // Percent symbols (%) must be double-encoded.
131
            $key   = $this->encode($key);
132
            $value = $this->encode($value);
133
134
            $normalized[$key] = $value;
135
        }
136
137
        return $normalized;
138
    }
139
140
    /**
141
     * Process filters.
142
     *
143
     * @param array $parameters Request parameters.
144
     *
145
     * @return array
146
     */
147
    protected function processFilters($parameters)
148
    {
149
        if (isset($parameters['filter'])) {
150
            $filters = $parameters['filter'];
151
            unset($parameters['filter']);
152
            foreach ($filters as $filter => $value) {
153
                $parameters['filter[' . $filter . ']'] = $value;
154
            }
155
        }
156
157
        return $parameters;
158
    }
159
160
    /**
161
     * Get secret.
162
     *
163
     * @return string
164
     */
165
    protected function getSecret()
166
    {
167
        $secret = $this->consumerSecret;
168
169
        // Fix secret for v3 or later.
170
        if (!\in_array($this->apiVersion, ['v1', 'v2'])) {
171
            $secret .= '&';
172
        }
173
174
        return $secret;
175
    }
176
177
    /**
178
     * Generate oAuth1.0 signature.
179
     *
180
     * @param array $parameters Request parameters including oauth.
181
     *
182
     * @return string
183
     */
184
    protected function generateOauthSignature($parameters)
185
    {
186
        $baseRequestUri = \rawurlencode($this->url);
187
188
        // Extract filters.
189
        $parameters = $this->processFilters($parameters);
190
191
        // Normalize parameter key/values and sort them.
192
        $parameters = $this->normalizeParameters($parameters);
193
        \uksort($parameters, 'strcmp');
194
195
        // Set query string.
196
        $queryString  = \implode('%26', $this->joinWithEqualsSign($parameters)); // Join with ampersand.
197
        $stringToSign = $this->method . '&' . $baseRequestUri . '&' . $queryString;
198
        $secret       = $this->getSecret();
199
200
        return \base64_encode(\hash_hmac(self::HASH_ALGORITHM, $stringToSign, $secret, true));
201
    }
202
203
    /**
204
     * Creates an array of urlencoded strings out of each array key/value pairs.
205
     *
206
     * @param  array  $params      Array of parameters to convert.
207
     * @param  array  $queryParams Array to extend.
208
     * @param  string $key         Optional Array key to append
209
     * @return string              Array of urlencoded strings
210
     */
211
    protected function joinWithEqualsSign($params, $queryParams = [], $key = '')
212
    {
213
        foreach ($params as $paramKey => $paramValue) {
214
            if ($key) {
215
                $paramKey = $key . '%5B' . $paramKey . '%5D'; // Handle multi-dimensional array.
216
            }
217
218
            if (is_array($paramValue)) {
219
                $queryParams = $this->joinWithEqualsSign($paramValue, $queryParams, $paramKey);
220
            } else {
221
                $string = $paramKey . '=' . $paramValue; // Join with equals sign.
222
                $queryParams[] = $this->encode($string);
223
            }
224
        }
225
226
        return $queryParams;
227
    }
228
229
    /**
230
     * Sort parameters.
231
     *
232
     * @param array $parameters Parameters to sort in byte-order.
233
     *
234
     * @return array
235
     */
236
    protected function getSortedParameters($parameters)
237
    {
238
        \uksort($parameters, 'strcmp');
239
240
        foreach ($parameters as $key => $value) {
241
            if (\is_array($value)) {
242
                \uksort($parameters[$key], 'strcmp');
243
            }
244
        }
245
246
        return $parameters;
247
    }
248
249
    /**
250
     * Get oAuth1.0 parameters.
251
     *
252
     * @return string
253
     */
254
    public function getParameters()
255
    {
256
        $parameters = \array_merge($this->parameters, [
257
            'oauth_consumer_key'     => $this->consumerKey,
258
            'oauth_timestamp'        => $this->timestamp,
259
            'oauth_nonce'            => \sha1(\microtime()),
260
            'oauth_signature_method' => 'HMAC-' . self::HASH_ALGORITHM,
261
        ]);
262
263
        // The parameters above must be included in the signature generation.
264
        $parameters['oauth_signature'] = $this->generateOauthSignature($parameters);
265
266
        return $this->getSortedParameters($parameters);
267
    }
268
}
269