Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
105 / 105
100.00% covered (success)
100.00%
12 / 12
CRAP
100.00% covered (success)
100.00%
1 / 1
OAuthServer
100.00% covered (success)
100.00%
105 / 105
100.00% covered (success)
100.00%
12 / 12
29
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 add_signature_method
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fetch_request_token
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 fetch_access_token
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 verify_request
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 get_version
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 get_signature_method
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
4
 get_consumer
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 get_token
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
 check_signature
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
4
 check_timestamp
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 check_nonce
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2// vim: foldmethod=marker
3/**
4 * The MIT License
5 *
6 * Copyright (c) 2007 Andy Smith
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files ( the "Software" ), to deal
10 * in the Software without restriction, including without limitation the rights
11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 * THE SOFTWARE.
25 */
26
27namespace MediaWiki\Extension\OAuth\Lib;
28
29use MediaWiki\Extension\OAuth\Lib\OAuthDataStore;
30use MediaWiki\Extension\OAuth\Lib\OAuthException;
31use MediaWiki\Extension\OAuth\Lib\OAuthRequest;
32use MediaWiki\Logger\LoggerFactory;
33use Psr\Log\LoggerInterface;
34
35class OAuthServer {
36    protected $timestamp_threshold = 300; // in seconds, five minutes
37    protected $version = '1.0'; // hi blaine
38    protected $signature_methods = array();
39
40    /** @var OAuthDataStore */
41    protected $data_store;
42
43    /** @var LoggerInterface */
44    protected $logger;
45
46    function __construct( $data_store ) {
47        $this->data_store = $data_store;
48        $this->logger = LoggerFactory::getInstance( 'OAuth' );
49    }
50
51    public function add_signature_method( $signature_method ) {
52        $this->signature_methods[$signature_method->get_name()] = $signature_method;
53    }
54
55    // high level functions
56
57    /**
58     * process a request_token request
59     * returns the request token on success
60     */
61    public function fetch_request_token( &$request ) {
62        $this->get_version( $request );
63
64        $consumer = $this->get_consumer( $request );
65
66        // no token required for the initial token request
67        $token = null;
68
69        $this->check_signature( $request, $consumer, $token );
70
71        // Rev A change
72        $callback = $request->get_parameter( 'oauth_callback' );
73        $new_token = $this->data_store->new_request_token( $consumer, $callback );
74
75        return $new_token;
76    }
77
78    /**
79     * process an access_token request
80     * returns the access token on success
81     */
82    public function fetch_access_token( &$request ) {
83        $this->get_version( $request );
84
85        $consumer = $this->get_consumer( $request );
86
87        // requires authorized request token
88        $token = $this->get_token( $request, $consumer, "request" );
89
90        $this->check_signature( $request, $consumer, $token );
91
92        // Rev A change
93        $verifier = $request->get_parameter( 'oauth_verifier' );
94        $new_token = $this->data_store->new_access_token( $token, $consumer, $verifier );
95
96        return $new_token;
97    }
98
99    /**
100     * verify an api call, checks all the parameters
101     */
102    public function verify_request( &$request ) {
103        $this->get_version( $request );
104        $consumer = $this->get_consumer( $request );
105        $token = $this->get_token( $request, $consumer, "access" );
106        $this->check_signature( $request, $consumer, $token );
107
108        return array(
109            $consumer,
110            $token
111        );
112    }
113
114    // Internals from here
115
116    /**
117     * version 1
118     */
119    protected function get_version( &$request ) {
120        $version = $request->get_parameter( "oauth_version" );
121        if ( !$version ) {
122            // Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present.
123            // Chapter 7.0 ( "Accessing Protected Ressources" )
124            $version = '1.0';
125        }
126        if ( $version !== $this->version ) {
127            throw new OAuthException( "OAuth version '$version' not supported" );
128        }
129
130        return $version;
131    }
132
133    /**
134     * figure out the signature with some defaults
135     */
136    private function get_signature_method( $request ) {
137        $signature_method = $request instanceof OAuthRequest ? $request->get_parameter(
138            "oauth_signature_method"
139        ) : null;
140
141        if ( !$signature_method ) {
142            // According to chapter 7 ( "Accessing Protected Ressources" ) the signature-method
143            // parameter is required, and we can't just fallback to PLAINTEXT
144            throw new OAuthException( 'No signature method parameter. This parameter is required' );
145        }
146
147        if ( !in_array( $signature_method, array_keys( $this->signature_methods ) ) ) {
148            throw new OAuthException(
149                "Signature method '$signature_method' not supported " . "try one of the following: " . implode(
150                    ", ",
151                    array_keys( $this->signature_methods )
152                )
153            );
154        }
155
156        return $this->signature_methods[$signature_method];
157    }
158
159    /**
160     * try to find the consumer for the provided request's consumer key
161     */
162    protected function get_consumer( $request ) {
163        $consumer_key = $request instanceof OAuthRequest ? $request->get_parameter(
164            "oauth_consumer_key"
165        ) : null;
166
167        if ( !$consumer_key ) {
168            throw new OAuthException( "Invalid consumer key" );
169        }
170        $this->logger->debug( __METHOD__ . ": getting consumer for '$consumer_key'" );
171        $consumer = $this->data_store->lookup_consumer( $consumer_key );
172        if ( !$consumer ) {
173            throw new OAuthException( "Invalid consumer" );
174        }
175
176        return $consumer;
177    }
178
179    /**
180     * try to find the token for the provided request's token key
181     */
182    protected function get_token( $request, $consumer, $token_type = "access" ) {
183        $token_field = $request instanceof OAuthRequest ? $request->get_parameter(
184            'oauth_token'
185        ) : null;
186
187        $token = $this->data_store->lookup_token(
188            $consumer,
189            $token_type,
190            $token_field
191        );
192        if ( !$token ) {
193            throw new OAuthException( "Invalid $token_type token: $token_field" );
194        }
195
196        return $token;
197    }
198
199    /**
200     * all-in-one function to check the signature on a request
201     * should guess the signature method appropriately
202     */
203    protected function check_signature( $request, $consumer, $token ) {
204        // this should probably be in a different method
205        $timestamp = $request instanceof OAuthRequest ? $request->get_parameter(
206            'oauth_timestamp'
207        ) : null;
208        $nonce = $request instanceof OAuthRequest ? $request->get_parameter( 'oauth_nonce' ) : null;
209
210        $this->check_timestamp( $timestamp );
211        $this->check_nonce( $consumer, $token, $nonce, $timestamp );
212
213        $signature_method = $this->get_signature_method( $request );
214        $signature = $request->get_parameter( 'oauth_signature' );
215        $valid_sig = $signature_method->check_signature(
216            $request,
217            $consumer,
218            $token,
219            $signature
220        );
221
222        if ( !$valid_sig ) {
223            $this->logger->info(
224                __METHOD__ . ': Signature check (' . get_class( $signature_method ) . ') failed'
225            );
226            throw new OAuthException( "Invalid signature" );
227        }
228    }
229
230    /**
231     * check that the timestamp is new enough
232     */
233    private function check_timestamp( $timestamp ) {
234        if ( !$timestamp ) {
235            throw new OAuthException(
236                'Missing timestamp parameter. The parameter is required'
237            );
238        }
239
240        // verify that timestamp is recentish
241        $now = time();
242        if ( abs( $now - $timestamp ) > $this->timestamp_threshold ) {
243            throw new OAuthException(
244                "Expired timestamp, yours $timestamp, ours $now"
245            );
246        }
247    }
248
249    /**
250     * check that the nonce is not repeated
251     */
252    private function check_nonce( $consumer, $token, $nonce, $timestamp ) {
253        if ( !$nonce ) {
254            throw new OAuthException(
255                'Missing nonce parameter. The parameter is required'
256            );
257        }
258
259        // verify that the nonce is uniqueish
260        $found = $this->data_store->lookup_nonce(
261            $consumer,
262            $token,
263            $nonce,
264            $timestamp
265        );
266        if ( $found ) {
267            throw new OAuthException( "Nonce already used: $nonce" );
268        }
269    }
270
271}