1 <?php
2 /**
3 * @package Joomla.Platform
4 * @subpackage OAuth1
5 *
6 * @copyright Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 */
9
10 defined('JPATH_PLATFORM') or die();
11
12 use Joomla\Registry\Registry;
13
14 /**
15 * Joomla Platform class for interacting with an OAuth 1.0 and 1.0a server.
16 *
17 * @since 13.1
18 * @deprecated 4.0 Use the `joomla/oauth1` framework package that will be bundled instead
19 */
20 abstract class JOAuth1Client
21 {
22 /**
23 * @var Registry Options for the JOAuth1Client object.
24 * @since 13.1
25 */
26 protected $options;
27
28 /**
29 * @var array Contains access token key, secret and verifier.
30 * @since 13.1
31 */
32 protected $token = array();
33
34 /**
35 * @var JHttp The HTTP client object to use in sending HTTP requests.
36 * @since 13.1
37 */
38 protected $client;
39
40 /**
41 * @var JInput The input object to use in retrieving GET/POST data.
42 * @since 13.1
43 */
44 protected $input;
45
46 /**
47 * @var JApplicationWeb The application object to send HTTP headers for redirects.
48 * @since 13.1
49 */
50 protected $application;
51
52 /**
53 * @var string Selects which version of OAuth to use: 1.0 or 1.0a.
54 * @since 13.1
55 */
56 protected $version;
57
58 /**
59 * Constructor.
60 *
61 * @param Registry $options OAuth1Client options object.
62 * @param JHttp $client The HTTP client object.
63 * @param JInput $input The input object
64 * @param JApplicationWeb $application The application object
65 * @param string $version Specify the OAuth version. By default we are using 1.0a.
66 *
67 * @since 13.1
68 */
69 public function __construct(Registry $options = null, JHttp $client = null, JInput $input = null, JApplicationWeb $application = null,
70 $version = null)
71 {
72 $this->options = isset($options) ? $options : new Registry;
73 $this->client = isset($client) ? $client : JHttpFactory::getHttp($this->options);
74 $this->input = isset($input) ? $input : JFactory::getApplication()->input;
75 $this->application = isset($application) ? $application : new JApplicationWeb;
76 $this->version = isset($version) ? $version : '1.0a';
77 }
78
79 /**
80 * Method to for the oauth flow.
81 *
82 * @return array Contains access token key, secret and verifier.
83 *
84 * @since 13.1
85 * @throws DomainException
86 */
87 public function authenticate()
88 {
89 // Already got some credentials stored?
90 if ($this->token)
91 {
92 $response = $this->verifyCredentials();
93
94 if ($response)
95 {
96 return $this->token;
97 }
98 else
99 {
100 $this->token = null;
101 }
102 }
103
104 // Check for callback.
105 if (strcmp($this->version, '1.0a') === 0)
106 {
107 $verifier = $this->input->get('oauth_verifier');
108 }
109 else
110 {
111 $verifier = $this->input->get('oauth_token');
112 }
113
114 if (empty($verifier))
115 {
116 // Generate a request token.
117 $this->_generateRequestToken();
118
119 // Authenticate the user and authorise the app.
120 $this->_authorise();
121 }
122
123 // Callback
124 else
125 {
126 $session = JFactory::getSession();
127
128 // Get token form session.
129 $this->token = array('key' => $session->get('key', null, 'oauth_token'), 'secret' => $session->get('secret', null, 'oauth_token'));
130
131 // Verify the returned request token.
132 if (strcmp($this->token['key'], $this->input->get('oauth_token')) !== 0)
133 {
134 throw new DomainException('Bad session!');
135 }
136
137 // Set token verifier for 1.0a.
138 if (strcmp($this->version, '1.0a') === 0)
139 {
140 $this->token['verifier'] = $this->input->get('oauth_verifier');
141 }
142
143 // Generate access token.
144 $this->_generateAccessToken();
145
146 // Return the access token.
147 return $this->token;
148 }
149 }
150
151 /**
152 * Method used to get a request token.
153 *
154 * @return void
155 *
156 * @since 13.1
157 * @throws DomainException
158 */
159 private function _generateRequestToken()
160 {
161 // Set the callback URL.
162 if ($this->getOption('callback'))
163 {
164 $parameters = array(
165 'oauth_callback' => $this->getOption('callback'),
166 );
167 }
168 else
169 {
170 $parameters = array();
171 }
172
173 // Make an OAuth request for the Request Token.
174 $response = $this->oauthRequest($this->getOption('requestTokenURL'), 'POST', $parameters);
175
176 parse_str($response->body, $params);
177
178 if (strcmp($this->version, '1.0a') === 0 && strcmp($params['oauth_callback_confirmed'], 'true') !== 0)
179 {
180 throw new DomainException('Bad request token!');
181 }
182
183 // Save the request token.
184 $this->token = array('key' => $params['oauth_token'], 'secret' => $params['oauth_token_secret']);
185
186 // Save the request token in session
187 $session = JFactory::getSession();
188 $session->set('key', $this->token['key'], 'oauth_token');
189 $session->set('secret', $this->token['secret'], 'oauth_token');
190 }
191
192 /**
193 * Method used to authorise the application.
194 *
195 * @return void
196 *
197 * @since 13.1
198 */
199 private function _authorise()
200 {
201 $url = $this->getOption('authoriseURL') . '?oauth_token=' . $this->token['key'];
202
203 if ($this->getOption('scope'))
204 {
205 $scope = is_array($this->getOption('scope')) ? implode(' ', $this->getOption('scope')) : $this->getOption('scope');
206 $url .= '&scope=' . urlencode($scope);
207 }
208
209 if ($this->getOption('sendheaders'))
210 {
211 $this->application->redirect($url);
212 }
213 }
214
215 /**
216 * Method used to get an access token.
217 *
218 * @return void
219 *
220 * @since 13.1
221 */
222 private function _generateAccessToken()
223 {
224 // Set the parameters.
225 $parameters = array(
226 'oauth_token' => $this->token['key'],
227 );
228
229 if (strcmp($this->version, '1.0a') === 0)
230 {
231 $parameters = array_merge($parameters, array('oauth_verifier' => $this->token['verifier']));
232 }
233
234 // Make an OAuth request for the Access Token.
235 $response = $this->oauthRequest($this->getOption('accessTokenURL'), 'POST', $parameters);
236
237 parse_str($response->body, $params);
238
239 // Save the access token.
240 $this->token = array('key' => $params['oauth_token'], 'secret' => $params['oauth_token_secret']);
241 }
242
243 /**
244 * Method used to make an OAuth request.
245 *
246 * @param string $url The request URL.
247 * @param string $method The request method.
248 * @param array $parameters Array containing request parameters.
249 * @param mixed $data The POST request data.
250 * @param array $headers An array of name-value pairs to include in the header of the request
251 *
252 * @return JHttpResponse
253 *
254 * @since 13.1
255 * @throws DomainException
256 */
257 public function oauthRequest($url, $method, $parameters, $data = array(), $headers = array())
258 {
259 // Set the parameters.
260 $defaults = array(
261 'oauth_consumer_key' => $this->getOption('consumer_key'),
262 'oauth_signature_method' => 'HMAC-SHA1',
263 'oauth_version' => '1.0',
264 'oauth_nonce' => $this->generateNonce(),
265 'oauth_timestamp' => time(),
266 );
267
268 $parameters = array_merge($parameters, $defaults);
269
270 // Do not encode multipart parameters. Do not include $data in the signature if $data is not array.
271 if (isset($headers['Content-Type']) && strpos($headers['Content-Type'], 'multipart/form-data') !== false || !is_array($data))
272 {
273 $oauth_headers = $parameters;
274 }
275 else
276 {
277 // Use all parameters for the signature.
278 $oauth_headers = array_merge($parameters, $data);
279 }
280
281 // Sign the request.
282 $oauth_headers = $this->_signRequest($url, $method, $oauth_headers);
283
284 // Get parameters for the Authorisation header.
285 if (is_array($data))
286 {
287 $oauth_headers = array_diff_key($oauth_headers, $data);
288 }
289
290 // Send the request.
291 switch ($method)
292 {
293 case 'GET':
294 $url = $this->toUrl($url, $data);
295 $response = $this->client->get($url, array('Authorization' => $this->_createHeader($oauth_headers)));
296 break;
297 case 'POST':
298 $headers = array_merge($headers, array('Authorization' => $this->_createHeader($oauth_headers)));
299 $response = $this->client->post($url, $data, $headers);
300 break;
301 case 'PUT':
302 $headers = array_merge($headers, array('Authorization' => $this->_createHeader($oauth_headers)));
303 $response = $this->client->put($url, $data, $headers);
304 break;
305 case 'DELETE':
306 $headers = array_merge($headers, array('Authorization' => $this->_createHeader($oauth_headers)));
307 $response = $this->client->delete($url, $headers);
308 break;
309 }
310
311 // Validate the response code.
312 $this->validateResponse($url, $response);
313
314 return $response;
315 }
316
317 /**
318 * Method to validate a response.
319 *
320 * @param string $url The request URL.
321 * @param JHttpResponse $response The response to validate.
322 *
323 * @return void
324 *
325 * @since 13.1
326 * @throws DomainException
327 */
328 abstract public function validateResponse($url, $response);
329
330 /**
331 * Method used to create the header for the POST request.
332 *
333 * @param array $parameters Array containing request parameters.
334 *
335 * @return string The header.
336 *
337 * @since 13.1
338 */
339 private function _createHeader($parameters)
340 {
341 $header = 'OAuth ';
342
343 foreach ($parameters as $key => $value)
344 {
345 if (!strcmp($header, 'OAuth '))
346 {
347 $header .= $key . '="' . $this->safeEncode($value) . '"';
348 }
349 else
350 {
351 $header .= ', ' . $key . '="' . $value . '"';
352 }
353 }
354
355 return $header;
356 }
357
358 /**
359 * Method to create the URL formed string with the parameters.
360 *
361 * @param string $url The request URL.
362 * @param array $parameters Array containing request parameters.
363 *
364 * @return string The formed URL.
365 *
366 * @since 13.1
367 */
368 public function toUrl($url, $parameters)
369 {
370 foreach ($parameters as $key => $value)
371 {
372 if (is_array($value))
373 {
374 foreach ($value as $v)
375 {
376 if (strpos($url, '?') === false)
377 {
378 $url .= '?' . $key . '=' . $v;
379 }
380 else
381 {
382 $url .= '&' . $key . '=' . $v;
383 }
384 }
385 }
386 else
387 {
388 if (strpos($value, ' ') !== false)
389 {
390 $value = $this->safeEncode($value);
391 }
392
393 if (strpos($url, '?') === false)
394 {
395 $url .= '?' . $key . '=' . $value;
396 }
397 else
398 {
399 $url .= '&' . $key . '=' . $value;
400 }
401 }
402 }
403
404 return $url;
405 }
406
407 /**
408 * Method used to sign requests.
409 *
410 * @param string $url The URL to sign.
411 * @param string $method The request method.
412 * @param array $parameters Array containing request parameters.
413 *
414 * @return array
415 *
416 * @since 13.1
417 */
418 private function _signRequest($url, $method, $parameters)
419 {
420 // Create the signature base string.
421 $base = $this->_baseString($url, $method, $parameters);
422
423 $parameters['oauth_signature'] = $this->safeEncode(
424 base64_encode(
425 hash_hmac('sha1', $base, $this->_prepareSigningKey(), true)
426 )
427 );
428
429 return $parameters;
430 }
431
432 /**
433 * Prepare the signature base string.
434 *
435 * @param string $url The URL to sign.
436 * @param string $method The request method.
437 * @param array $parameters Array containing request parameters.
438 *
439 * @return string The base string.
440 *
441 * @since 13.1
442 */
443 private function _baseString($url, $method, $parameters)
444 {
445 // Sort the parameters alphabetically
446 uksort($parameters, 'strcmp');
447
448 // Encode parameters.
449 foreach ($parameters as $key => $value)
450 {
451 $key = $this->safeEncode($key);
452
453 if (is_array($value))
454 {
455 foreach ($value as $v)
456 {
457 $v = $this->safeEncode($v);
458 $kv[] = "{$key}={$v}";
459 }
460 }
461 else
462 {
463 $value = $this->safeEncode($value);
464 $kv[] = "{$key}={$value}";
465 }
466 }
467 // Form the parameter string.
468 $params = implode('&', $kv);
469
470 // Signature base string elements.
471 $base = array(
472 $method,
473 $url,
474 $params,
475 );
476
477 // Return the base string.
478 return implode('&', $this->safeEncode($base));
479 }
480
481 /**
482 * Encodes the string or array passed in a way compatible with OAuth.
483 * If an array is passed each array value will will be encoded.
484 *
485 * @param mixed $data The scalar or array to encode.
486 *
487 * @return string $data encoded in a way compatible with OAuth.
488 *
489 * @since 13.1
490 */
491 public function safeEncode($data)
492 {
493 if (is_array($data))
494 {
495 return array_map(array($this, 'safeEncode'), $data);
496 }
497 elseif (is_scalar($data))
498 {
499 return str_ireplace(
500 array('+', '%7E'),
501 array(' ', '~'),
502 rawurlencode($data)
503 );
504 }
505 else
506 {
507 return '';
508 }
509 }
510
511 /**
512 * Method used to generate the current nonce.
513 *
514 * @return string The current nonce.
515 *
516 * @since 13.1
517 */
518 public static function generateNonce()
519 {
520 $mt = microtime();
521 $rand = JCrypt::genRandomBytes();
522
523 // The md5s look nicer than numbers.
524 return md5($mt . $rand);
525 }
526
527 /**
528 * Prepares the OAuth signing key.
529 *
530 * @return string The prepared signing key.
531 *
532 * @since 13.1
533 */
534 private function _prepareSigningKey()
535 {
536 return $this->safeEncode($this->getOption('consumer_secret')) . '&' . $this->safeEncode(($this->token) ? $this->token['secret'] : '');
537 }
538
539 /**
540 * Returns an HTTP 200 OK response code and a representation of the requesting user if authentication was successful;
541 * returns a 401 status code and an error message if not.
542 *
543 * @return array The decoded JSON response
544 *
545 * @since 13.1
546 */
547 abstract public function verifyCredentials();
548
549 /**
550 * Get an option from the JOauth1aClient instance.
551 *
552 * @param string $key The name of the option to get
553 *
554 * @return mixed The option value
555 *
556 * @since 13.1
557 */
558 public function getOption($key)
559 {
560 return $this->options->get($key);
561 }
562
563 /**
564 * Set an option for the JOauth1aClient instance.
565 *
566 * @param string $key The name of the option to set
567 * @param mixed $value The option value to set
568 *
569 * @return JOAuth1Client This object for method chaining
570 *
571 * @since 13.1
572 */
573 public function setOption($key, $value)
574 {
575 $this->options->set($key, $value);
576
577 return $this;
578 }
579
580 /**
581 * Get the oauth token key or secret.
582 *
583 * @return array The oauth token key and secret.
584 *
585 * @since 13.1
586 */
587 public function getToken()
588 {
589 return $this->token;
590 }
591
592 /**
593 * Set the oauth token.
594 *
595 * @param array $token The access token key and secret.
596 *
597 * @return JOAuth1Client This object for method chaining.
598 *
599 * @since 13.1
600 */
601 public function setToken($token)
602 {
603 $this->token = $token;
604
605 return $this;
606 }
607 }
608