Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
110 / 110
100.00% covered (success)
100.00%
17 / 17
CRAP
100.00% covered (success)
100.00%
1 / 1
Request
100.00% covered (success)
100.00%
110 / 110
100.00% covered (success)
100.00%
17 / 17
50
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 fromRequest
100.00% covered (success)
100.00%
25 / 25
100.00% covered (success)
100.00%
1 / 1
11
 fromConsumerAndToken
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
 setParameter
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 getParameter
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getParameters
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 unsetParameter
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSignableParameters
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getSignatureBaseString
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 getNormalizedMethod
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getNormalizedUrl
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
10
 toUrl
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 toPostData
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 toHeader
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
6
 __toString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 signRequest
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 buildSignature
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * @section LICENSE
4 * Copyright (c) 2007 Andy Smith
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining
7 * a copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including without
9 * limitation the rights to use, copy, modify, merge, publish, distribute,
10 * sublicense, and/or sell copies of the Software, and to permit persons to
11 * whom the Software is furnished to do so, subject to the following
12 * conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 * DEALINGS IN THE SOFTWARE.
24 *
25 * @file
26 */
27
28namespace MediaWiki\OAuthClient;
29
30/**
31 * An OAuth request
32 */
33class Request {
34    /**
35     * @var array
36     */
37    protected $parameters;
38
39    /**
40     * @var string
41     */
42    protected $method;
43
44    /**
45     * @var string
46     */
47    protected $url;
48
49    /**
50     * @var string
51     */
52    public static $version = '1.0';
53
54    /**
55     * Used for tests.
56     * @var string
57     */
58    public static $POST_INPUT = 'php://input';
59
60    /**
61     * @param string $method
62     * @param string $url
63     * @param array|null $parameters
64     */
65    public function __construct( $method, $url, $parameters = null ) {
66        $parameters = $parameters ?: [];
67        $parameters = array_merge(
68            Util::parseParameters( parse_url( $url, PHP_URL_QUERY ) ),
69            $parameters
70        );
71        $this->parameters = $parameters;
72        $this->method = $method;
73        $this->url = $url;
74    }
75
76    /**
77     * Attempt to build up a request from what was passed to the server
78     *
79     * @param string|null $method
80     * @param string|null $url
81     * @param array|null $params
82     * @return Request
83     */
84    public static function fromRequest(
85        $method = null,
86        $url = null,
87        array $params = null
88    ) {
89        $scheme = ( !isset( $_SERVER['HTTPS'] ) || $_SERVER['HTTPS'] != 'on' ) ?
90            'http' : 'https';
91        $url = ( $url ?: $scheme ) .
92            '://' . $_SERVER['SERVER_NAME'] .
93            ':' .
94            $_SERVER['SERVER_PORT'] .
95            $_SERVER['REQUEST_URI'];
96        $method = $method ?: $_SERVER['REQUEST_METHOD'];
97
98        // We weren't handed any params, so let's find the ones relevant
99        // to this request. If you run XML-RPC or similar you should use this
100        // to provide your own parsed parameter-list
101        if ( !$params ) {
102            // Find request headers
103            $headers = Util::getHeaders();
104
105            // Parse the query-string to find GET params
106            $params = Util::parseParameters( $_SERVER['QUERY_STRING'] );
107
108            // It's a POST request of the proper content-type, so parse POST
109            // params and add those overriding any duplicates from GET
110            if ( $method === 'POST' &&
111                isset( $headers['Content-Type'] ) &&
112                strstr( $headers['Content-Type'],
113                    'application/x-www-form-urlencoded'
114                )
115            ) {
116                $post_data = Util::parseParameters(
117                    file_get_contents( self::$POST_INPUT )
118                );
119                $params = array_merge( $params, $post_data );
120            }
121
122            // We have a Authorization-header with OAuth data. Parse the header
123            // and add those overriding any duplicates from GET or POST
124            if ( isset( $headers['Authorization'] ) &&
125                substr( $headers['Authorization'], 0, 6 ) === 'OAuth '
126            ) {
127                $header_params = Util::splitHeader( $headers['Authorization'] );
128                $params = array_merge( $params, $header_params );
129            }
130        }
131
132        return new Request( $method, $url, $params );
133    }
134
135    /**
136     * @param Consumer $consumer
137     * @param Token|null $token
138     * @param string $method
139     * @param string $url
140     * @param array|null $parameters
141     * @return Request
142     */
143    public static function fromConsumerAndToken(
144        Consumer $consumer,
145        ?Token $token,
146        $method,
147        $url,
148        array $parameters = null
149    ) {
150        $parameters = $parameters ?: [];
151        $defaults = [
152            'oauth_version' => static::$version,
153            'oauth_nonce' => md5( microtime() . mt_rand() ),
154            'oauth_timestamp' => time(),
155            'oauth_consumer_key' => $consumer->key,
156        ];
157        if ( $token ) {
158            $defaults['oauth_token'] = $token->key;
159        }
160        $parameters = array_merge( $defaults, $parameters );
161
162        return new self( $method, $url, $parameters );
163    }
164
165    /**
166     * @param string $name
167     * @param string $value
168     * @param bool $allow_duplicates
169     */
170    public function setParameter( $name, $value, $allow_duplicates = true ) {
171        if ( $allow_duplicates && isset( $this->parameters[$name] ) ) {
172            // We have already added parameter(s) with this name, so add to
173            // the list
174            if ( is_scalar( $this->parameters[$name] ) ) {
175                // This is the first duplicate, so transform scalar (string)
176                // into an array so we can add the duplicates
177                $this->parameters[$name] = [ $this->parameters[$name] ];
178            }
179
180            $this->parameters[$name][] = $value;
181        } else {
182            $this->parameters[$name] = $value;
183        }
184    }
185
186    /**
187     * @param string $name
188     * @return mixed
189     */
190    public function getParameter( $name ) {
191        return isset( $this->parameters[$name] ) ?
192            $this->parameters[$name] : null;
193    }
194
195    /**
196     * @return array
197     */
198    public function getParameters() {
199        return $this->parameters;
200    }
201
202    /**
203     * @param string $name
204     */
205    public function unsetParameter( $name ) {
206        unset( $this->parameters[$name] );
207    }
208
209    /**
210     * The request parameters, sorted and concatenated into a normalized string.
211     * @return string
212     */
213    public function getSignableParameters() {
214        // Grab all parameters
215        $params = $this->parameters;
216
217        // Remove oauth_signature if present
218        // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.")
219        if ( isset( $params['oauth_signature'] ) ) {
220            unset( $params['oauth_signature'] );
221        }
222
223        return Util::buildHttpQuery( $params );
224    }
225
226    /**
227     * Returns the base string of this request
228     *
229     * The base string defined as the method, the url
230     * and the parameters (normalized), each urlencoded
231     * and the concated with &.
232     * @return string
233     */
234    public function getSignatureBaseString() {
235        $parts = [
236            $this->getNormalizedMethod(),
237            $this->getNormalizedUrl(),
238            $this->getSignableParameters()
239        ];
240
241        $parts = Util::urlencode( $parts );
242
243        return implode( '&', $parts );
244    }
245
246    /**
247     * @return string
248     */
249    public function getNormalizedMethod() {
250        return strtoupper( $this->method );
251    }
252
253    /**
254     * Parses the url and rebuilds it to be scheme://host/path
255     * @return string
256     */
257    public function getNormalizedUrl() {
258        $parts = parse_url( $this->url );
259
260        $scheme = isset( $parts['scheme'] ) ? $parts['scheme'] : 'http';
261        $port = isset( $parts['port'] ) ?
262            $parts['port'] : ( $scheme === 'https' ? '443' : '80' );
263        $host = isset( $parts['host'] ) ? strtolower( $parts['host'] ) : '';
264        $path = isset( $parts['path'] ) ? $parts['path'] : '';
265
266        if ( ( $scheme === 'https' && $port != '443' ) ||
267            ( $scheme === 'http' && $port != '80' )
268        ) {
269            $host = "{$host}:{$port}";
270        }
271        return "{$scheme}://{$host}{$path}";
272    }
273
274    /**
275     * Builds a url usable for a GET request
276     * @return string
277     */
278    public function toUrl() {
279        $post_data = $this->toPostData();
280        $out = $this->getNormalizedUrl();
281        if ( $post_data ) {
282            $out .= '?' . $post_data;
283        }
284        return $out;
285    }
286
287    /**
288     * Builds the data one would send in a POST request
289     * @return string
290     */
291    public function toPostData() {
292        return Util::buildHttpQuery( $this->parameters );
293    }
294
295    /**
296     * Builds the Authorization: header
297     * @param string|null $realm
298     * @return string
299     */
300    public function toHeader( $realm = null ) {
301        $first = true;
302        if ( $realm ) {
303            $out = 'Authorization: OAuth realm="' .
304                Util::urlencode( $realm ) . '"';
305            $first = false;
306        } else {
307            $out = 'Authorization: OAuth';
308        }
309
310        foreach ( $this->parameters as $k => $v ) {
311            if ( substr( $k, 0, 5 ) !== 'oauth' ) {
312                continue;
313            }
314            if ( is_array( $v ) ) {
315                throw new Exception( 'Arrays not supported in headers' );
316            }
317            $out .= ( $first ) ? ' ' : ',';
318            $out .= Util::urlencode( $k ) . '="' . Util::urlencode( $v ) . '"';
319            $first = false;
320        }
321        return $out;
322    }
323
324    public function __toString() {
325        return $this->toUrl();
326    }
327
328    /**
329     * @param SignatureMethod $signature_method
330     * @param Consumer $consumer
331     * @param Token|null $token
332     */
333    public function signRequest(
334        SignatureMethod $signature_method,
335        Consumer $consumer,
336        Token $token = null
337    ) {
338        $this->setParameter(
339            'oauth_signature_method',
340            $signature_method->getName(),
341            false
342        );
343        $signature = $this->buildSignature(
344            $signature_method, $consumer, $token
345        );
346        $this->setParameter( 'oauth_signature', $signature, false );
347    }
348
349    /**
350     * @param SignatureMethod $signature_method
351     * @param Consumer $consumer
352     * @param Token|null $token
353     * @return mixed
354     */
355    public function buildSignature(
356        SignatureMethod $signature_method,
357        Consumer $consumer,
358        Token $token = null
359    ) {
360        $signature = $signature_method->buildSignature(
361            $this, $consumer, $token
362        );
363        return $signature;
364    }
365}