PluginProbe
Loginizer / 1.9.9
Loginizer v1.9.9
2.1.0 2.0.9 2.0.8 1.9.8 1.9.9 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 trunk 1.0 1.0.1 1.0.2 1.1.0 1.1.1 1.2.0 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 All 74 releases
loginizer / lib / hybridauth / Adapter / OAuth1.php

OAuth1.php in Loginizer 1.9.9, at lib/hybridauth/Adapter/OAuth1.php

617 lines 18.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*!
3 * Hybridauth
4 * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth
5 * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html
6 */
7
8 namespace Hybridauth\Adapter;
9
10 use Hybridauth\Exception\Exception;
11 use Hybridauth\Exception\InvalidApplicationCredentialsException;
12 use Hybridauth\Exception\AuthorizationDeniedException;
13 use Hybridauth\Exception\InvalidOauthTokenException;
14 use Hybridauth\Exception\InvalidAccessTokenException;
15 use Hybridauth\Data;
16 use Hybridauth\HttpClient;
17 use Hybridauth\Thirdparty\OAuth\OAuthConsumer;
18 use Hybridauth\Thirdparty\OAuth\OAuthRequest;
19 use Hybridauth\Thirdparty\OAuth\OAuthSignatureMethodHMACSHA1;
20 use Hybridauth\Thirdparty\OAuth\OAuthUtil;
21
22 /**
23 * This class can be used to simplify the authorization flow of OAuth 1 based service providers.
24 *
25 * Subclasses (i.e., providers adapters) can either use the already provided methods or override
26 * them when necessary.
27 */
28 abstract class OAuth1 extends AbstractAdapter implements AdapterInterface
29 {
30 /**
31 * Base URL to provider API
32 *
33 * This var will be used to build urls when sending signed requests
34 *
35 * @var string
36 */
37 protected $apiBaseUrl = '';
38
39 /**
40 * @var string
41 */
42 protected $authorizeUrl = '';
43
44 /**
45 * @var string
46 */
47 protected $requestTokenUrl = '';
48
49 /**
50 * @var string
51 */
52 protected $accessTokenUrl = '';
53
54 /**
55 * IPD API Documentation
56 *
57 * OPTIONAL.
58 *
59 * @var string
60 */
61 protected $apiDocumentation = '';
62
63 /**
64 * OAuth Version
65 *
66 * '1.0' OAuth Core 1.0
67 * '1.0a' OAuth Core 1.0 Revision A
68 *
69 * @var string
70 */
71 protected $oauth1Version = '1.0a';
72
73 /**
74 * @var string
75 */
76 protected $consumerKey = null;
77
78 /**
79 * @var string
80 */
81 protected $consumerSecret = null;
82
83 /**
84 * @var object
85 */
86 protected $OAuthConsumer = null;
87
88 /**
89 * @var object
90 */
91 protected $sha1Method = null;
92
93 /**
94 * @var object
95 */
96 protected $consumerToken = null;
97
98 /**
99 * Authorization Url Parameters
100 *
101 * @var bool
102 */
103 protected $AuthorizeUrlParameters = [];
104
105 /**
106 * @var string
107 */
108 protected $requestTokenMethod = 'POST';
109
110 /**
111 * @var array
112 */
113 protected $requestTokenParameters = [];
114
115 /**
116 * @var array
117 */
118 protected $requestTokenHeaders = [];
119
120 /**
121 * @var string
122 */
123 protected $tokenExchangeMethod = 'POST';
124
125 /**
126 * @var array
127 */
128 protected $tokenExchangeParameters = [];
129
130 /**
131 * @var array
132 */
133 protected $tokenExchangeHeaders = [];
134
135 /**
136 * @var array
137 */
138 protected $apiRequestParameters = [];
139
140 /**
141 * @var array
142 */
143 protected $apiRequestHeaders = [];
144
145 /**
146 * {@inheritdoc}
147 */
148 protected function configure()
149 {
150 $this->consumerKey = $this->config->filter('keys')->get('id') ?: $this->config->filter('keys')->get('key');
151 $this->consumerSecret = $this->config->filter('keys')->get('secret');
152
153 if (!$this->consumerKey || !$this->consumerSecret) {
154 throw new InvalidApplicationCredentialsException(
155 'Your application id is required in order to connect to ' . $this->providerId
156 );
157 }
158
159 if ($this->config->exists('tokens')) {
160 $this->setAccessToken($this->config->get('tokens'));
161 }
162
163 $this->setCallback($this->config->get('callback'));
164 $this->setApiEndpoints($this->config->get('endpoints'));
165 }
166
167 /**
168 * {@inheritdoc}
169 */
170 protected function initialize()
171 {
172 /**
173 * Set up OAuth Signature and Consumer
174 *
175 * OAuth Core: All Token requests and Protected Resources requests MUST be signed
176 * by the Consumer and verified by the Service Provider.
177 *
178 * The protocol defines three signature methods: HMAC-SHA1, RSA-SHA1, and PLAINTEXT..
179 *
180 * The Consumer declares a signature method in the oauth_signature_method parameter..
181 *
182 * http://oauth.net/core/1.0a/#signing_process
183 */
184 $this->sha1Method = new OAuthSignatureMethodHMACSHA1();
185
186 $this->OAuthConsumer = new OAuthConsumer(
187 $this->consumerKey,
188 $this->consumerSecret
189 );
190
191 if ($this->getStoredData('request_token')) {
192 $this->consumerToken = new OAuthConsumer(
193 $this->getStoredData('request_token'),
194 $this->getStoredData('request_token_secret')
195 );
196 }
197
198 if ($this->getStoredData('access_token')) {
199 $this->consumerToken = new OAuthConsumer(
200 $this->getStoredData('access_token'),
201 $this->getStoredData('access_token_secret')
202 );
203 }
204 }
205
206 /**
207 * {@inheritdoc}
208 */
209 public function authenticate()
210 {
211 $this->logger->info(sprintf('%s::authenticate()', get_class($this)));
212
213 if ($this->isConnected()) {
214 return true;
215 }
216
217 try {
218 if (!$this->getStoredData('request_token')) {
219 // Start a new flow.
220 $this->authenticateBegin();
221 } elseif (empty($_GET['oauth_token']) && empty($_GET['denied'])) {
222 // A previous authentication was not finished, and this request is not finishing it.
223 $this->authenticateBegin();
224 } else {
225 // Finish a flow.
226 $this->authenticateFinish();
227 }
228 } catch (Exception $exception) {
229 $this->clearStoredData();
230
231 throw $exception;
232 }
233
234 return null;
235 }
236
237 /**
238 * {@inheritdoc}
239 */
240 public function isConnected()
241 {
242 return (bool)$this->getStoredData('access_token');
243 }
244
245 /**
246 * Initiate the authorization protocol
247 *
248 * 1. Obtaining an Unauthorized Request Token
249 * 2. Build Authorization URL for Authorization Request and redirect the user-agent to the
250 * Authorization Server.
251 */
252 protected function authenticateBegin()
253 {
254 $response = $this->requestAuthToken();
255
256 $this->validateAuthTokenRequest($response);
257
258 $authUrl = $this->getAuthorizeUrl();
259
260 $this->logger->debug(sprintf('%s::authenticateBegin(), redirecting user to:', get_class($this)), [$authUrl]);
261
262 HttpClient\Util::redirect($authUrl);
263 }
264
265 /**
266 * Finalize the authorization process
267 *
268 * @throws AuthorizationDeniedException
269 * @throws \Hybridauth\Exception\HttpClientFailureException
270 * @throws \Hybridauth\Exception\HttpRequestFailedException
271 * @throws InvalidAccessTokenException
272 * @throws InvalidOauthTokenException
273 */
274 protected function authenticateFinish()
275 {
276 $this->logger->debug(
277 sprintf('%s::authenticateFinish(), callback url:', get_class($this)),
278 [HttpClient\Util::getCurrentUrl(true)]
279 );
280
281 $denied = filter_input(INPUT_GET, 'denied');
282 $oauth_problem = filter_input(INPUT_GET, 'oauth_problem');
283 $oauth_token = filter_input(INPUT_GET, 'oauth_token');
284 $oauth_verifier = filter_input(INPUT_GET, 'oauth_verifier');
285
286 if ($denied) {
287 throw new AuthorizationDeniedException(
288 'User denied access request. Provider returned a denied token: ' . htmlentities($denied)
289 );
290 }
291
292 if ($oauth_problem) {
293 throw new InvalidOauthTokenException(
294 'Provider returned an error. oauth_problem: ' . htmlentities($oauth_problem)
295 );
296 }
297
298 if (!$oauth_token) {
299 throw new InvalidOauthTokenException(
300 'Expecting a non-null oauth_token to continue the authorization flow.'
301 );
302 }
303
304 $response = $this->exchangeAuthTokenForAccessToken($oauth_token, $oauth_verifier);
305
306 $this->validateAccessTokenExchange($response);
307
308 $this->initialize();
309 }
310
311 /**
312 * Build Authorization URL for Authorization Request
313 *
314 * @param array $parameters
315 *
316 * @return string
317 */
318 protected function getAuthorizeUrl($parameters = [])
319 {
320 $this->AuthorizeUrlParameters = !empty($parameters)
321 ? $parameters
322 : array_replace(
323 (array)$this->AuthorizeUrlParameters,
324 (array)$this->config->get('authorize_url_parameters')
325 );
326
327 $this->AuthorizeUrlParameters['oauth_token'] = $this->getStoredData('request_token');
328
329 return $this->authorizeUrl . '?' . http_build_query($this->AuthorizeUrlParameters, '', '&');
330 }
331
332 /**
333 * Unauthorized Request Token
334 *
335 * OAuth Core: The Consumer obtains an unauthorized Request Token by asking the Service Provider
336 * to issue a Token. The Request Token's sole purpose is to receive User approval and can only
337 * be used to obtain an Access Token.
338 *
339 * http://oauth.net/core/1.0/#auth_step1
340 * 6.1.1. Consumer Obtains a Request Token
341 *
342 * @return string Raw Provider API response
343 * @throws \Hybridauth\Exception\HttpClientFailureException
344 * @throws \Hybridauth\Exception\HttpRequestFailedException
345 */
346 protected function requestAuthToken()
347 {
348 /**
349 * OAuth Core 1.0 Revision A: oauth_callback: An absolute URL to which the Service Provider will redirect
350 * the User back when the Obtaining User Authorization step is completed.
351 *
352 * http://oauth.net/core/1.0a/#auth_step1
353 */
354 if ('1.0a' == $this->oauth1Version) {
355 $this->requestTokenParameters['oauth_callback'] = $this->callback;
356 }
357
358 $response = $this->oauthRequest(
359 $this->requestTokenUrl,
360 $this->requestTokenMethod,
361 $this->requestTokenParameters,
362 $this->requestTokenHeaders
363 );
364
365 return $response;
366 }
367
368 /**
369 * Validate Unauthorized Request Token Response
370 *
371 * OAuth Core: The Service Provider verifies the signature and Consumer Key. If successful,
372 * it generates a Request Token and Token Secret and returns them to the Consumer in the HTTP
373 * response body.
374 *
375 * http://oauth.net/core/1.0/#auth_step1
376 * 6.1.2. Service Provider Issues an Unauthorized Request Token
377 *
378 * @param string $response
379 *
380 * @return \Hybridauth\Data\Collection
381 * @throws InvalidOauthTokenException
382 */
383 protected function validateAuthTokenRequest($response)
384 {
385 /**
386 * The response contains the following parameters:
387 *
388 * - oauth_token The Request Token.
389 * - oauth_token_secret The Token Secret.
390 * - oauth_callback_confirmed MUST be present and set to true.
391 *
392 * http://oauth.net/core/1.0/#auth_step1
393 * 6.1.2. Service Provider Issues an Unauthorized Request Token
394 *
395 * Example of a successful response:
396 *
397 * HTTP/1.1 200 OK
398 * Content-Type: text/html; charset=utf-8
399 * Cache-Control: no-store
400 * Pragma: no-cache
401 *
402 * oauth_token=80359084-clg1DEtxQF3wstTcyUdHF3wsdHM&oauth_token_secret=OIF07hPmJB:P
403 * 6qiHTi1znz6qiH3tTcyUdHnz6qiH3tTcyUdH3xW3wsDvV08e&example_parameter=example_value
404 *
405 * OAuthUtil::parse_parameters will attempt to decode the raw response into an array.
406 */
407 $tokens = OAuthUtil::parse_parameters($response);
408
409 $collection = new Data\Collection($tokens);
410
411 if (!$collection->exists('oauth_token')) {
412 throw new InvalidOauthTokenException(
413 'Provider returned no oauth_token: ' . htmlentities($response)
414 );
415 }
416
417 $this->consumerToken = new OAuthConsumer(
418 $tokens['oauth_token'],
419 $tokens['oauth_token_secret']
420 );
421
422 $this->storeData('request_token', $tokens['oauth_token']);
423 $this->storeData('request_token_secret', $tokens['oauth_token_secret']);
424
425 return $collection;
426 }
427
428 /**
429 * Requests an Access Token
430 *
431 * OAuth Core: The Request Token and Token Secret MUST be exchanged for an Access Token and Token Secret.
432 *
433 * http://oauth.net/core/1.0a/#auth_step3
434 * 6.3.1. Consumer Requests an Access Token
435 *
436 * @param string $oauth_token
437 * @param string $oauth_verifier
438 *
439 * @return string Raw Provider API response
440 * @throws \Hybridauth\Exception\HttpClientFailureException
441 * @throws \Hybridauth\Exception\HttpRequestFailedException
442 */
443 protected function exchangeAuthTokenForAccessToken($oauth_token, $oauth_verifier = '')
444 {
445 $this->tokenExchangeParameters['oauth_token'] = $oauth_token;
446
447 /**
448 * OAuth Core 1.0 Revision A: oauth_verifier: The verification code received from the Service Provider
449 * in the "Service Provider Directs the User Back to the Consumer" step.
450 *
451 * http://oauth.net/core/1.0a/#auth_step3
452 */
453 if ('1.0a' == $this->oauth1Version) {
454 $this->tokenExchangeParameters['oauth_verifier'] = $oauth_verifier;
455 }
456
457 $response = $this->oauthRequest(
458 $this->accessTokenUrl,
459 $this->tokenExchangeMethod,
460 $this->tokenExchangeParameters,
461 $this->tokenExchangeHeaders
462 );
463
464 return $response;
465 }
466
467 /**
468 * Validate Access Token Response
469 *
470 * OAuth Core: If successful, the Service Provider generates an Access Token and Token Secret and returns
471 * them in the HTTP response body.
472 *
473 * The Access Token and Token Secret are stored by the Consumer and used when signing Protected Resources requests.
474 *
475 * http://oauth.net/core/1.0a/#auth_step3
476 * 6.3.2. Service Provider Grants an Access Token
477 *
478 * @param string $response
479 *
480 * @return \Hybridauth\Data\Collection
481 * @throws InvalidAccessTokenException
482 */
483 protected function validateAccessTokenExchange($response)
484 {
485 /**
486 * The response contains the following parameters:
487 *
488 * - oauth_token The Access Token.
489 * - oauth_token_secret The Token Secret.
490 *
491 * http://oauth.net/core/1.0/#auth_step3
492 * 6.3.2. Service Provider Grants an Access Token
493 *
494 * Example of a successful response:
495 *
496 * HTTP/1.1 200 OK
497 * Content-Type: text/html; charset=utf-8
498 * Cache-Control: no-store
499 * Pragma: no-cache
500 *
501 * oauth_token=sHeLU7Far428zj8PzlWR75&oauth_token_secret=fXb30rzoG&oauth_callback_confirmed=true
502 *
503 * OAuthUtil::parse_parameters will attempt to decode the raw response into an array.
504 */
505 $tokens = OAuthUtil::parse_parameters($response);
506
507 $collection = new Data\Collection($tokens);
508
509 if (!$collection->exists('oauth_token')) {
510 throw new InvalidAccessTokenException(
511 'Provider returned no access_token: ' . htmlentities($response)
512 );
513 }
514
515 $this->consumerToken = new OAuthConsumer(
516 $collection->get('oauth_token'),
517 $collection->get('oauth_token_secret')
518 );
519
520 $this->storeData('access_token', $collection->get('oauth_token'));
521 $this->storeData('access_token_secret', $collection->get('oauth_token_secret'));
522
523 $this->deleteStoredData('request_token');
524 $this->deleteStoredData('request_token_secret');
525
526 return $collection;
527 }
528
529 /**
530 * Send a signed request to provider API
531 *
532 * Note: Since the specifics of error responses is beyond the scope of RFC6749 and OAuth specifications,
533 * Hybridauth will consider any HTTP status code that is different than '200 OK' as an ERROR.
534 *
535 * @param string $url
536 * @param string $method
537 * @param array $parameters
538 * @param array $headers
539 * @param bool $multipart
540 *
541 * @return mixed
542 * @throws \Hybridauth\Exception\HttpClientFailureException
543 * @throws \Hybridauth\Exception\HttpRequestFailedException
544 */
545 public function apiRequest($url, $method = 'GET', $parameters = [], $headers = [], $multipart = false)
546 {
547 // refresh tokens if needed
548 $this->maintainToken();
549
550 if (strrpos($url, 'http://') !== 0 && strrpos($url, 'https://') !== 0) {
551 $url = rtrim($this->apiBaseUrl, '/') . '/' . ltrim($url, '/');
552 }
553
554 $parameters = array_replace($this->apiRequestParameters, (array)$parameters);
555
556 $headers = array_replace($this->apiRequestHeaders, (array)$headers);
557
558 $response = $this->oauthRequest($url, $method, $parameters, $headers, $multipart);
559
560 $response = (new Data\Parser())->parse($response);
561
562 return $response;
563 }
564
565 /**
566 * Setup and Send a Signed Oauth Request
567 *
568 * This method uses OAuth Library.
569 *
570 * @param string $uri
571 * @param string $method
572 * @param array $parameters
573 * @param array $headers
574 * @param bool $multipart
575 *
576 * @return string Raw Provider API response
577 * @throws \Hybridauth\Exception\HttpClientFailureException
578 * @throws \Hybridauth\Exception\HttpRequestFailedException
579 */
580 protected function oauthRequest($uri, $method = 'GET', $parameters = [], $headers = [], $multipart = false)
581 {
582 $signing_parameters = $parameters;
583 if ($multipart) {
584 $signing_parameters = [];
585 }
586
587 $request = OAuthRequest::from_consumer_and_token(
588 $this->OAuthConsumer,
589 $this->consumerToken,
590 $method,
591 $uri,
592 $signing_parameters
593 );
594
595 $request->sign_request(
596 $this->sha1Method,
597 $this->OAuthConsumer,
598 $this->consumerToken
599 );
600
601 $uri = $request->get_normalized_http_url();
602 $headers = array_replace($request->to_header(), (array)$headers);
603
604 $response = $this->httpClient->request(
605 $uri,
606 $method,
607 $parameters,
608 $headers,
609 $multipart
610 );
611
612 $this->validateApiResponse('Signed API request to ' . $uri . ' has returned an error');
613
614 return $response;
615 }
616 }
617