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