Completed
Push — master ( cfdeee...8f80b3 )
by
unknown
03:05
created

OAuth::joinWithEqualsSign()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 16
rs 9.2
cc 4
eloc 10
nc 5
nop 3
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($url, $consumerKey, $consumerSecret, $apiVersion, $method, $parameters = [], $timestamp = '')
85
    {
86
        $this->url            = $url;
87
        $this->consumerKey    = $consumerKey;
88
        $this->consumerSecret = $consumerSecret;
89
        $this->apiVersion     = $apiVersion;
90
        $this->method         = $method;
91
        $this->parameters     = $parameters;
92
        $this->timestamp      = $timestamp;
93
    }
94
95
    /**
96
     * Encode according to RFC 3986.
97
     *
98
     * @param string|array $value Value to be normalized.
99
     *
100
     * @return string
101
     */
102
    protected function encode($value)
103
    {
104
        if (is_array($value)) {
105
            return array_map([$this, 'encode'], $value);
106
        } else {
107
            return str_replace(['+', '%7E'], [' ', '~'], rawurlencode($value));
108
        }
109
    }
110
111
    /**
112
     * Normalize parameters.
113
     *
114
     * @param array $parameters Parameters to normalize.
115
     *
116
     * @return array
117
     */
118
    protected function normalizeParameters($parameters)
119
    {
120
        $normalized = [];
121
122
        foreach ($parameters as $key => $value) {
123
            // Percent symbols (%) must be double-encoded.
124
            $key   = $this->encode($key);
125
            $value = $this->encode($value);
126
127
            $normalized[$key] = $value;
128
        }
129
130
        return $normalized;
131
    }
132
133
    /**
134
     * Process filters.
135
     *
136
     * @param array $parameters Request parameters.
137
     *
138
     * @return array
139
     */
140
    protected function processFilters($parameters)
141
    {
142
        if (isset($parameters['filter'])) {
143
            $filters = $parameters['filter'];
144
            unset($parameters['filter']);
145
            foreach ($filters as $filter => $value) {
146
                $parameters['filter[' . $filter . ']'] = $value;
147
            }
148
        }
149
150
        return $parameters;
151
    }
152
153
    /**
154
     * Get secret.
155
     *
156
     * @return string
157
     */
158
    protected function getSecret()
159
    {
160
        $secret = $this->consumerSecret;
161
162
        // Fix secret for v3 or later.
163
        if (!\in_array($this->apiVersion, ['v1', 'v2'])) {
164
            $secret .= '&';
165
        }
166
167
        return $secret;
168
    }
169
170
    /**
171
     * Generate oAuth1.0 signature.
172
     *
173
     * @param array $parameters Request parameters including oauth.
174
     *
175
     * @return string
176
     */
177
    protected function generateOauthSignature($parameters)
178
    {
179
        $baseRequestUri = \rawurlencode($this->url);
180
181
        // Extract filters.
182
        $parameters = $this->processFilters($parameters);
183
184
        // Normalize parameter key/values and sort them.
185
        $parameters = $this->normalizeParameters($parameters);
186
        \uksort($parameters, 'strcmp');
187
188
        // Set query string.
189
        $queryString  = \implode('%26', $this->joinWithEqualsSign($parameters)); // Join with ampersand.
190
        $stringToSign = $this->method . '&' . $baseRequestUri . '&' . $queryString;
191
        $secret       = $this->getSecret();
192
193
        return \base64_encode(\hash_hmac(self::HASH_ALGORITHM, $stringToSign, $secret, true));
194
    }
195
196
    /**
197
     * Creates an array of urlencoded strings out of each array key/value pairs.
198
     *
199
     * @param  array  $params      Array of parameters to convert.
200
     * @param  array  $queryParams Array to extend.
201
     * @param  string $key         Optional Array key to append
202
     * @return string              Array of urlencoded strings
203
     */
204
    protected function joinWithEqualsSign($params, $queryParams = [], $key = '') {
205
        foreach ( $params as $paramKey => $paramValue ) {
206
            if ( $key ) {
207
                $paramKey = $key . '%5B' . $paramKey . '%5D'; // Handle multi-dimensional array.
208
            }
209
210
            if ( is_array( $paramValue ) ) {
211
                $queryParams = $this->joinWithEqualsSign( $paramValue, $queryParams, $paramKey );
212
            } else {
213
                $string = $paramKey . '=' . $paramValue; // Join with equals sign.
214
                $queryParams[] = $this->encode( $string );
215
            }
216
        }
217
218
        return $queryParams;
219
    }
220
221
    /**
222
     * Sort parameters.
223
     *
224
     * @param array $parameters Parameters to sort in byte-order.
225
     *
226
     * @return array
227
     */
228
    protected function getSortedParameters($parameters)
229
    {
230
        \uksort($parameters, 'strcmp');
231
232
        foreach ($parameters as $key => $value) {
233
            if (\is_array($value)) {
234
                \uksort($parameters[$key], 'strcmp');
235
            }
236
        }
237
238
        return $parameters;
239
    }
240
241
    /**
242
     * Get oAuth1.0 parameters.
243
     *
244
     * @return string
245
     */
246
    public function getParameters()
247
    {
248
        $parameters = \array_merge($this->parameters, [
249
            'oauth_consumer_key'     => $this->consumerKey,
250
            'oauth_timestamp'        => $this->timestamp,
251
            'oauth_nonce'            => \sha1(\microtime()),
252
            'oauth_signature_method' => 'HMAC-' . self::HASH_ALGORITHM,
253
        ]);
254
255
        // The parameters above must be included in the signature generation.
256
        $parameters['oauth_signature'] = $this->generateOauthSignature($parameters);
257
258
        return $this->getSortedParameters($parameters);
259
    }
260
}
261