Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
105 / 105 |
|
100.00% |
12 / 12 |
CRAP | |
100.00% |
1 / 1 |
OAuthServer | |
100.00% |
105 / 105 |
|
100.00% |
12 / 12 |
29 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
add_signature_method | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
fetch_request_token | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
fetch_access_token | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
verify_request | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
get_version | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
get_signature_method | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
4 | |||
get_consumer | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
4 | |||
get_token | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
3 | |||
check_signature | |
100.00% |
19 / 19 |
|
100.00% |
1 / 1 |
4 | |||
check_timestamp | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
3 | |||
check_nonce | |
100.00% |
12 / 12 |
|
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 | |
27 | namespace MediaWiki\Extension\OAuth\Lib; |
28 | |
29 | use MediaWiki\Extension\OAuth\Lib\OAuthDataStore; |
30 | use MediaWiki\Extension\OAuth\Lib\OAuthException; |
31 | use MediaWiki\Extension\OAuth\Lib\OAuthRequest; |
32 | use MediaWiki\Logger\LoggerFactory; |
33 | use Psr\Log\LoggerInterface; |
34 | |
35 | class 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 | } |