Passed
Push — master ( 14fbd7...a7a005 )
by
unknown
55s queued 15s
created
src/Adapter/OAuth2.php 2 patches
Indentation   +716 added lines, -716 removed lines patch added patch discarded remove patch
@@ -24,721 +24,721 @@
 block discarded – undo
24 24
  */
25 25
 abstract class OAuth2 extends AbstractAdapter implements AdapterInterface
26 26
 {
27
-    /**
28
-     * Client Identifier
29
-     *
30
-     * RFC6749: client_id REQUIRED. The client identifier issued to the client during
31
-     * the registration process described by Section 2.2.
32
-     *
33
-     * http://tools.ietf.org/html/rfc6749#section-2.2
34
-     *
35
-     * @var string
36
-     */
37
-    protected $clientId = '';
38
-
39
-    /**
40
-     * Client Secret
41
-     *
42
-     * RFC6749: client_secret REQUIRED. The client secret. The client MAY omit the
43
-     * parameter if the client secret is an empty string.
44
-     *
45
-     * http://tools.ietf.org/html/rfc6749#section-2.2
46
-     *
47
-     * @var string
48
-     */
49
-    protected $clientSecret = '';
50
-
51
-    /**
52
-     * Access Token Scope
53
-     *
54
-     * RFC6749: The authorization and token endpoints allow the client to specify the
55
-     * scope of the access request using the "scope" request parameter.
56
-     *
57
-     * http://tools.ietf.org/html/rfc6749#section-3.3
58
-     *
59
-     * @var string
60
-     */
61
-    protected $scope = '';
62
-
63
-    /**
64
-     * Base URL to provider API
65
-     *
66
-     * This var will be used to build urls when sending signed requests
67
-     *
68
-     * @var string
69
-     */
70
-    protected $apiBaseUrl = '';
71
-
72
-    /**
73
-     * Authorization Endpoint
74
-     *
75
-     * RFC6749: The authorization endpoint is used to interact with the resource
76
-     * owner and obtain an authorization grant.
77
-     *
78
-     * http://tools.ietf.org/html/rfc6749#section-3.1
79
-     *
80
-     * @var string
81
-     */
82
-    protected $authorizeUrl = '';
83
-
84
-    /**
85
-     * Access Token Endpoint
86
-     *
87
-     * RFC6749: The token endpoint is used by the client to obtain an access token by
88
-     * presenting its authorization grant or refresh token.
89
-     *
90
-     * http://tools.ietf.org/html/rfc6749#section-3.2
91
-     *
92
-     * @var string
93
-     */
94
-    protected $accessTokenUrl = '';
95
-
96
-    /**
97
-     * TokenInfo endpoint
98
-     *
99
-     * Access token validation. OPTIONAL.
100
-     *
101
-     * @var string
102
-     */
103
-    protected $accessTokenInfoUrl = '';
104
-
105
-    /**
106
-     * IPD API Documentation
107
-     *
108
-     * OPTIONAL.
109
-     *
110
-     * @var string
111
-     */
112
-    protected $apiDocumentation = '';
113
-
114
-    /**
115
-     * Redirection Endpoint or Callback
116
-     *
117
-     * RFC6749: After completing its interaction with the resource owner, the
118
-     * authorization server directs the resource owner's user-agent back to
119
-     * the client.
120
-     *
121
-     * http://tools.ietf.org/html/rfc6749#section-3.1.2
122
-     *
123
-     * @var string
124
-     */
125
-    protected $callback = '';
126
-
127
-    /**
128
-     * Authorization Url Parameters
129
-     *
130
-     * @var array
131
-     */
132
-    protected $AuthorizeUrlParameters = [];
133
-
134
-
135
-    /**
136
-     * Authorization Url Parameter encoding type
137
-     * @see https://www.php.net/manual/de/function.http-build-query.php
138
-     *
139
-     * @var string
140
-     */
141
-    protected $AuthorizeUrlParametersEncType = PHP_QUERY_RFC1738;
142
-
143
-    /**
144
-     * Authorization Request State
145
-     *
146
-     * @var bool
147
-     */
148
-    protected $supportRequestState = true;
149
-
150
-    /**
151
-     * Access Token name
152
-     *
153
-     * While most providers will use 'access_token' as name for the Access Token attribute, other do not.
154
-     * On the latter case, this should be set by sub classes.
155
-     *
156
-     * @var string
157
-     */
158
-    protected $accessTokenName = 'access_token';
159
-
160
-    /**
161
-     * Authorization Request HTTP method.
162
-     *
163
-     * @see exchangeCodeForAccessToken()
164
-     *
165
-     * @var string
166
-     */
167
-    protected $tokenExchangeMethod = 'POST';
168
-
169
-    /**
170
-     * Authorization Request URL parameters.
171
-     *
172
-     * Sub classes may change add any additional parameter when necessary.
173
-     *
174
-     * @see exchangeCodeForAccessToken()
175
-     *
176
-     * @var array
177
-     */
178
-    protected $tokenExchangeParameters = [];
179
-
180
-    /**
181
-     * Authorization Request HTTP headers.
182
-     *
183
-     * Sub classes may add any additional header when necessary.
184
-     *
185
-     * @see exchangeCodeForAccessToken()
186
-     *
187
-     * @var array
188
-     */
189
-    protected $tokenExchangeHeaders = [];
190
-
191
-    /**
192
-     * Refresh Token Request HTTP method.
193
-     *
194
-     * @see refreshAccessToken()
195
-     *
196
-     * @var string
197
-     */
198
-    protected $tokenRefreshMethod = 'POST';
199
-
200
-    /**
201
-     * Refresh Token Request URL parameters.
202
-     *
203
-     * Sub classes may change add any additional parameter when necessary.
204
-     *
205
-     * @see refreshAccessToken()
206
-     *
207
-     * @var array|null
208
-     */
209
-    protected $tokenRefreshParameters = null;
210
-
211
-    /**
212
-     * Refresh Token Request HTTP headers.
213
-     *
214
-     * Sub classes may add any additional header when necessary.
215
-     *
216
-     * @see refreshAccessToken()
217
-     *
218
-     * @var array
219
-     */
220
-    protected $tokenRefreshHeaders = [];
221
-
222
-    /**
223
-     * Authorization Request URL parameters.
224
-     *
225
-     * Sub classes may change add any additional parameter when necessary.
226
-     *
227
-     * @see apiRequest()
228
-     *
229
-     * @var array
230
-     */
231
-    protected $apiRequestParameters = [];
232
-
233
-    /**
234
-     * Authorization Request HTTP headers.
235
-     *
236
-     * Sub classes may add any additional header when necessary.
237
-     *
238
-     * @see apiRequest()
239
-     *
240
-     * @var array
241
-     */
242
-    protected $apiRequestHeaders = [];
243
-
244
-    /**
245
-     * {@inheritdoc}
246
-     */
247
-    protected function configure()
248
-    {
249
-        $this->clientId = $this->config->filter('keys')->get('id') ?: $this->config->filter('keys')->get('key');
250
-        $this->clientSecret = $this->config->filter('keys')->get('secret');
251
-
252
-        if (!$this->clientId || !$this->clientSecret) {
253
-            throw new InvalidApplicationCredentialsException(
254
-                'Your application id is required in order to connect to ' . $this->providerId
255
-            );
256
-        }
257
-
258
-        $this->scope = $this->config->exists('scope') ? $this->config->get('scope') : $this->scope;
259
-
260
-        if ($this->config->exists('tokens')) {
261
-            $this->setAccessToken($this->config->get('tokens'));
262
-        }
27
+	/**
28
+	 * Client Identifier
29
+	 *
30
+	 * RFC6749: client_id REQUIRED. The client identifier issued to the client during
31
+	 * the registration process described by Section 2.2.
32
+	 *
33
+	 * http://tools.ietf.org/html/rfc6749#section-2.2
34
+	 *
35
+	 * @var string
36
+	 */
37
+	protected $clientId = '';
38
+
39
+	/**
40
+	 * Client Secret
41
+	 *
42
+	 * RFC6749: client_secret REQUIRED. The client secret. The client MAY omit the
43
+	 * parameter if the client secret is an empty string.
44
+	 *
45
+	 * http://tools.ietf.org/html/rfc6749#section-2.2
46
+	 *
47
+	 * @var string
48
+	 */
49
+	protected $clientSecret = '';
50
+
51
+	/**
52
+	 * Access Token Scope
53
+	 *
54
+	 * RFC6749: The authorization and token endpoints allow the client to specify the
55
+	 * scope of the access request using the "scope" request parameter.
56
+	 *
57
+	 * http://tools.ietf.org/html/rfc6749#section-3.3
58
+	 *
59
+	 * @var string
60
+	 */
61
+	protected $scope = '';
62
+
63
+	/**
64
+	 * Base URL to provider API
65
+	 *
66
+	 * This var will be used to build urls when sending signed requests
67
+	 *
68
+	 * @var string
69
+	 */
70
+	protected $apiBaseUrl = '';
71
+
72
+	/**
73
+	 * Authorization Endpoint
74
+	 *
75
+	 * RFC6749: The authorization endpoint is used to interact with the resource
76
+	 * owner and obtain an authorization grant.
77
+	 *
78
+	 * http://tools.ietf.org/html/rfc6749#section-3.1
79
+	 *
80
+	 * @var string
81
+	 */
82
+	protected $authorizeUrl = '';
83
+
84
+	/**
85
+	 * Access Token Endpoint
86
+	 *
87
+	 * RFC6749: The token endpoint is used by the client to obtain an access token by
88
+	 * presenting its authorization grant or refresh token.
89
+	 *
90
+	 * http://tools.ietf.org/html/rfc6749#section-3.2
91
+	 *
92
+	 * @var string
93
+	 */
94
+	protected $accessTokenUrl = '';
95
+
96
+	/**
97
+	 * TokenInfo endpoint
98
+	 *
99
+	 * Access token validation. OPTIONAL.
100
+	 *
101
+	 * @var string
102
+	 */
103
+	protected $accessTokenInfoUrl = '';
104
+
105
+	/**
106
+	 * IPD API Documentation
107
+	 *
108
+	 * OPTIONAL.
109
+	 *
110
+	 * @var string
111
+	 */
112
+	protected $apiDocumentation = '';
113
+
114
+	/**
115
+	 * Redirection Endpoint or Callback
116
+	 *
117
+	 * RFC6749: After completing its interaction with the resource owner, the
118
+	 * authorization server directs the resource owner's user-agent back to
119
+	 * the client.
120
+	 *
121
+	 * http://tools.ietf.org/html/rfc6749#section-3.1.2
122
+	 *
123
+	 * @var string
124
+	 */
125
+	protected $callback = '';
126
+
127
+	/**
128
+	 * Authorization Url Parameters
129
+	 *
130
+	 * @var array
131
+	 */
132
+	protected $AuthorizeUrlParameters = [];
133
+
134
+
135
+	/**
136
+	 * Authorization Url Parameter encoding type
137
+	 * @see https://www.php.net/manual/de/function.http-build-query.php
138
+	 *
139
+	 * @var string
140
+	 */
141
+	protected $AuthorizeUrlParametersEncType = PHP_QUERY_RFC1738;
142
+
143
+	/**
144
+	 * Authorization Request State
145
+	 *
146
+	 * @var bool
147
+	 */
148
+	protected $supportRequestState = true;
149
+
150
+	/**
151
+	 * Access Token name
152
+	 *
153
+	 * While most providers will use 'access_token' as name for the Access Token attribute, other do not.
154
+	 * On the latter case, this should be set by sub classes.
155
+	 *
156
+	 * @var string
157
+	 */
158
+	protected $accessTokenName = 'access_token';
159
+
160
+	/**
161
+	 * Authorization Request HTTP method.
162
+	 *
163
+	 * @see exchangeCodeForAccessToken()
164
+	 *
165
+	 * @var string
166
+	 */
167
+	protected $tokenExchangeMethod = 'POST';
168
+
169
+	/**
170
+	 * Authorization Request URL parameters.
171
+	 *
172
+	 * Sub classes may change add any additional parameter when necessary.
173
+	 *
174
+	 * @see exchangeCodeForAccessToken()
175
+	 *
176
+	 * @var array
177
+	 */
178
+	protected $tokenExchangeParameters = [];
179
+
180
+	/**
181
+	 * Authorization Request HTTP headers.
182
+	 *
183
+	 * Sub classes may add any additional header when necessary.
184
+	 *
185
+	 * @see exchangeCodeForAccessToken()
186
+	 *
187
+	 * @var array
188
+	 */
189
+	protected $tokenExchangeHeaders = [];
190
+
191
+	/**
192
+	 * Refresh Token Request HTTP method.
193
+	 *
194
+	 * @see refreshAccessToken()
195
+	 *
196
+	 * @var string
197
+	 */
198
+	protected $tokenRefreshMethod = 'POST';
199
+
200
+	/**
201
+	 * Refresh Token Request URL parameters.
202
+	 *
203
+	 * Sub classes may change add any additional parameter when necessary.
204
+	 *
205
+	 * @see refreshAccessToken()
206
+	 *
207
+	 * @var array|null
208
+	 */
209
+	protected $tokenRefreshParameters = null;
210
+
211
+	/**
212
+	 * Refresh Token Request HTTP headers.
213
+	 *
214
+	 * Sub classes may add any additional header when necessary.
215
+	 *
216
+	 * @see refreshAccessToken()
217
+	 *
218
+	 * @var array
219
+	 */
220
+	protected $tokenRefreshHeaders = [];
221
+
222
+	/**
223
+	 * Authorization Request URL parameters.
224
+	 *
225
+	 * Sub classes may change add any additional parameter when necessary.
226
+	 *
227
+	 * @see apiRequest()
228
+	 *
229
+	 * @var array
230
+	 */
231
+	protected $apiRequestParameters = [];
232
+
233
+	/**
234
+	 * Authorization Request HTTP headers.
235
+	 *
236
+	 * Sub classes may add any additional header when necessary.
237
+	 *
238
+	 * @see apiRequest()
239
+	 *
240
+	 * @var array
241
+	 */
242
+	protected $apiRequestHeaders = [];
243
+
244
+	/**
245
+	 * {@inheritdoc}
246
+	 */
247
+	protected function configure()
248
+	{
249
+		$this->clientId = $this->config->filter('keys')->get('id') ?: $this->config->filter('keys')->get('key');
250
+		$this->clientSecret = $this->config->filter('keys')->get('secret');
251
+
252
+		if (!$this->clientId || !$this->clientSecret) {
253
+			throw new InvalidApplicationCredentialsException(
254
+				'Your application id is required in order to connect to ' . $this->providerId
255
+			);
256
+		}
257
+
258
+		$this->scope = $this->config->exists('scope') ? $this->config->get('scope') : $this->scope;
259
+
260
+		if ($this->config->exists('tokens')) {
261
+			$this->setAccessToken($this->config->get('tokens'));
262
+		}
263 263
         
264
-        if ($this->config->exists('supportRequestState')) {
265
-            $this->supportRequestState = $this->config->get('supportRequestState');
266
-        }
267
-
268
-        $this->setCallback($this->config->get('callback'));
269
-        $this->setApiEndpoints($this->config->get('endpoints'));
270
-    }
271
-
272
-    /**
273
-     * {@inheritdoc}
274
-     */
275
-    protected function initialize()
276
-    {
277
-        $this->AuthorizeUrlParameters = [
278
-            'response_type' => 'code',
279
-            'client_id' => $this->clientId,
280
-            'redirect_uri' => $this->callback,
281
-            'scope' => $this->scope,
282
-        ];
283
-
284
-        $this->tokenExchangeParameters = [
285
-            'client_id' => $this->clientId,
286
-            'client_secret' => $this->clientSecret,
287
-            'grant_type' => 'authorization_code',
288
-            'redirect_uri' => $this->callback
289
-        ];
290
-
291
-        $refreshToken = $this->getStoredData('refresh_token');
292
-        if (!empty($refreshToken)) {
293
-            $this->tokenRefreshParameters = [
294
-                'grant_type' => 'refresh_token',
295
-                'refresh_token' => $refreshToken,
296
-            ];
297
-        }
298
-
299
-        $this->apiRequestHeaders = [
300
-            'Authorization' => 'Bearer ' . $this->getStoredData('access_token')
301
-        ];
302
-    }
303
-
304
-    /**
305
-     * {@inheritdoc}
306
-     */
307
-    public function authenticate()
308
-    {
309
-        $this->logger->info(sprintf('%s::authenticate()', get_class($this)));
310
-
311
-        if ($this->isConnected()) {
312
-            return true;
313
-        }
314
-
315
-        try {
316
-            $this->authenticateCheckError();
317
-
318
-            $code = filter_input($_SERVER['REQUEST_METHOD'] === 'POST' ? INPUT_POST : INPUT_GET, 'code');
319
-
320
-            if (empty($code)) {
321
-                $this->authenticateBegin();
322
-            } else {
323
-                $this->authenticateFinish();
324
-            }
325
-        } catch (Exception $e) {
326
-            $this->clearStoredData();
327
-
328
-            throw $e;
329
-        }
330
-
331
-        return null;
332
-    }
333
-
334
-    /**
335
-     * {@inheritdoc}
336
-     */
337
-    public function isConnected()
338
-    {
339
-        if ((bool)$this->getStoredData('access_token')) {
340
-            return (!$this->hasAccessTokenExpired() || $this->isRefreshTokenAvailable());
341
-        }
342
-        return false;
343
-    }
344
-
345
-    /**
346
-     * If we can use a refresh token, then an expired token does not stop us being connected.
347
-     *
348
-     * @return bool
349
-     */
350
-    public function isRefreshTokenAvailable()
351
-    {
352
-        return is_array($this->tokenRefreshParameters);
353
-    }
354
-
355
-    /**
356
-     * Authorization Request Error Response
357
-     *
358
-     * RFC6749: If the request fails due to a missing, invalid, or mismatching
359
-     * redirection URI, or if the client identifier is missing or invalid,
360
-     * the authorization server SHOULD inform the resource owner of the error.
361
-     *
362
-     * http://tools.ietf.org/html/rfc6749#section-4.1.2.1
363
-     *
364
-     * @throws \Hybridauth\Exception\InvalidAuthorizationCodeException
365
-     * @throws \Hybridauth\Exception\AuthorizationDeniedException
366
-     */
367
-    protected function authenticateCheckError()
368
-    {
369
-        $error = filter_input(INPUT_GET, 'error', FILTER_SANITIZE_SPECIAL_CHARS);
370
-
371
-        if (!empty($error)) {
372
-            $error_description = filter_input(INPUT_GET, 'error_description', FILTER_SANITIZE_SPECIAL_CHARS);
373
-            $error_uri = filter_input(INPUT_GET, 'error_uri', FILTER_SANITIZE_SPECIAL_CHARS);
374
-
375
-            $collated_error = sprintf('Provider returned an error: %s %s %s', $error, $error_description, $error_uri);
376
-
377
-            if ($error == 'access_denied') {
378
-                throw new AuthorizationDeniedException($collated_error);
379
-            }
380
-
381
-            throw new InvalidAuthorizationCodeException($collated_error);
382
-        }
383
-    }
384
-
385
-    /**
386
-     * Initiate the authorization protocol
387
-     *
388
-     * Build Authorization URL for Authorization Request and redirect the user-agent to the
389
-     * Authorization Server.
390
-     */
391
-    protected function authenticateBegin()
392
-    {
393
-        $authUrl = $this->getAuthorizeUrl();
394
-
395
-        $this->logger->debug(sprintf('%s::authenticateBegin(), redirecting user to:', get_class($this)), [$authUrl]);
396
-
397
-        HttpClient\Util::redirect($authUrl);
398
-    }
399
-
400
-    /**
401
-     * Finalize the authorization process
402
-     *
403
-     * @throws \Hybridauth\Exception\HttpClientFailureException
404
-     * @throws \Hybridauth\Exception\HttpRequestFailedException
405
-     * @throws InvalidAccessTokenException
406
-     * @throws InvalidAuthorizationStateException
407
-     */
408
-    protected function authenticateFinish()
409
-    {
410
-        $this->logger->debug(
411
-            sprintf('%s::authenticateFinish(), callback url:', get_class($this)),
412
-            [HttpClient\Util::getCurrentUrl(true)]
413
-        );
414
-
415
-        $state = filter_input($_SERVER['REQUEST_METHOD'] === 'POST' ? INPUT_POST : INPUT_GET, 'state');
416
-        $code = filter_input($_SERVER['REQUEST_METHOD'] === 'POST' ? INPUT_POST : INPUT_GET, 'code');
417
-
418
-        /**
419
-         * Authorization Request State
420
-         *
421
-         * RFC6749: state : RECOMMENDED. An opaque value used by the client to maintain
422
-         * state between the request and callback. The authorization server includes
423
-         * this value when redirecting the user-agent back to the client.
424
-         *
425
-         * http://tools.ietf.org/html/rfc6749#section-4.1.1
426
-         */
427
-        if ($this->supportRequestState
428
-            && (!$state || $this->getStoredData('authorization_state') != $state)
429
-        ) {
430
-            $this->deleteStoredData('authorization_state');
431
-            throw new InvalidAuthorizationStateException(
432
-                'The authorization state [state=' . substr(htmlentities($state), 0, 100) . '] '
433
-                . 'of this page is either invalid or has already been consumed.'
434
-            );
435
-        }
436
-
437
-        /**
438
-         * Authorization Request Code
439
-         *
440
-         * RFC6749: If the resource owner grants the access request, the authorization
441
-         * server issues an authorization code and delivers it to the client:
442
-         *
443
-         * http://tools.ietf.org/html/rfc6749#section-4.1.2
444
-         */
445
-        $response = $this->exchangeCodeForAccessToken($code);
446
-
447
-        $this->validateAccessTokenExchange($response);
448
-
449
-        $this->initialize();
450
-    }
451
-
452
-    /**
453
-     * Build Authorization URL for Authorization Request
454
-     *
455
-     * RFC6749: The client constructs the request URI by adding the following
456
-     * $parameters to the query component of the authorization endpoint URI:
457
-     *
458
-     *    - response_type  REQUIRED. Value MUST be set to "code".
459
-     *    - client_id      REQUIRED.
460
-     *    - redirect_uri   OPTIONAL.
461
-     *    - scope          OPTIONAL.
462
-     *    - state          RECOMMENDED.
463
-     *
464
-     * http://tools.ietf.org/html/rfc6749#section-4.1.1
465
-     *
466
-     * Sub classes may redefine this method when necessary.
467
-     *
468
-     * @param array $parameters
469
-     *
470
-     * @return string Authorization URL
471
-     */
472
-    protected function getAuthorizeUrl($parameters = [])
473
-    {
474
-        $this->AuthorizeUrlParameters = !empty($parameters)
475
-            ? $parameters
476
-            : array_replace(
477
-                (array)$this->AuthorizeUrlParameters,
478
-                (array)$this->config->get('authorize_url_parameters')
479
-            );
480
-
481
-        if ($this->supportRequestState) {
482
-            if (!isset($this->AuthorizeUrlParameters['state'])) {
483
-                $this->AuthorizeUrlParameters['state'] = 'HA-' . str_shuffle('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890');
484
-            }
485
-
486
-            $this->storeData('authorization_state', $this->AuthorizeUrlParameters['state']);
487
-        }
488
-
489
-        $queryParams = http_build_query($this->AuthorizeUrlParameters, '', '&', $this->AuthorizeUrlParametersEncType);
490
-        return $this->authorizeUrl . '?' . $queryParams;
491
-    }
492
-
493
-    /**
494
-     * Access Token Request
495
-     *
496
-     * This method will exchange the received $code in loginFinish() with an Access Token.
497
-     *
498
-     * RFC6749: The client makes a request to the token endpoint by sending the
499
-     * following parameters using the "application/x-www-form-urlencoded"
500
-     * with a character encoding of UTF-8 in the HTTP request entity-body:
501
-     *
502
-     *    - grant_type    REQUIRED. Value MUST be set to "authorization_code".
503
-     *    - code          REQUIRED. The authorization code received from the authorization server.
504
-     *    - redirect_uri  REQUIRED.
505
-     *    - client_id     REQUIRED.
506
-     *
507
-     * http://tools.ietf.org/html/rfc6749#section-4.1.3
508
-     *
509
-     * @param string $code
510
-     *
511
-     * @return string Raw Provider API response
512
-     * @throws \Hybridauth\Exception\HttpClientFailureException
513
-     * @throws \Hybridauth\Exception\HttpRequestFailedException
514
-     */
515
-    protected function exchangeCodeForAccessToken($code)
516
-    {
517
-        $this->tokenExchangeParameters['code'] = $code;
518
-
519
-        $response = $this->httpClient->request(
520
-            $this->accessTokenUrl,
521
-            $this->tokenExchangeMethod,
522
-            $this->tokenExchangeParameters,
523
-            $this->tokenExchangeHeaders
524
-        );
525
-
526
-        $this->validateApiResponse('Unable to exchange code for API access token');
527
-
528
-        return $response;
529
-    }
530
-
531
-    /**
532
-     * Validate Access Token Response
533
-     *
534
-     * RFC6749: If the access token request is valid and authorized, the
535
-     * authorization server issues an access token and optional refresh token.
536
-     * If the request client authentication failed or is invalid, the authorization
537
-     * server returns an error response as described in Section 5.2.
538
-     *
539
-     * Example of a successful response:
540
-     *
541
-     *  HTTP/1.1 200 OK
542
-     *  Content-Type: application/json;charset=UTF-8
543
-     *  Cache-Control: no-store
544
-     *  Pragma: no-cache
545
-     *
546
-     *  {
547
-     *      "access_token":"2YotnFZFEjr1zCsicMWpAA",
548
-     *      "token_type":"example",
549
-     *      "expires_in":3600,
550
-     *      "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
551
-     *      "example_parameter":"example_value"
552
-     *  }
553
-     *
554
-     * http://tools.ietf.org/html/rfc6749#section-4.1.4
555
-     *
556
-     * This method uses Data_Parser to attempt to decodes the raw $response (usually JSON)
557
-     * into a data collection.
558
-     *
559
-     * @param string $response
560
-     *
561
-     * @return \Hybridauth\Data\Collection
562
-     * @throws InvalidAccessTokenException
563
-     */
564
-    protected function validateAccessTokenExchange($response)
565
-    {
566
-        $data = (new Data\Parser())->parse($response);
567
-
568
-        $collection = new Data\Collection($data);
569
-
570
-        if (!$collection->exists('access_token')) {
571
-            throw new InvalidAccessTokenException(
572
-                'Provider returned no access_token: ' . htmlentities($response)
573
-            );
574
-        }
575
-
576
-        $this->storeData('access_token', $collection->get('access_token'));
577
-        $this->storeData('token_type', $collection->get('token_type'));
578
-
579
-        if ($collection->get('refresh_token')) {
580
-            $this->storeData('refresh_token', $collection->get('refresh_token'));
581
-        }
582
-
583
-        // calculate when the access token expire
584
-        if ($collection->exists('expires_in')) {
585
-            $expires_at = time() + (int)$collection->get('expires_in');
586
-
587
-            $this->storeData('expires_in', $collection->get('expires_in'));
588
-            $this->storeData('expires_at', $expires_at);
589
-        }
590
-
591
-        $this->deleteStoredData('authorization_state');
592
-
593
-        $this->initialize();
594
-
595
-        return $collection;
596
-    }
597
-
598
-    /**
599
-     * Refreshing an Access Token
600
-     *
601
-     * RFC6749: If the authorization server issued a refresh token to the
602
-     * client, the client makes a refresh request to the token endpoint by
603
-     * adding the following parameters ... in the HTTP request entity-body:
604
-     *
605
-     *    - grant_type     REQUIRED. Value MUST be set to "refresh_token".
606
-     *    - refresh_token  REQUIRED. The refresh token issued to the client.
607
-     *    - scope          OPTIONAL.
608
-     *
609
-     * http://tools.ietf.org/html/rfc6749#section-6
610
-     *
611
-     * This method is similar to exchangeCodeForAccessToken(). The only
612
-     * difference is here we exchange refresh_token for a new access_token.
613
-     *
614
-     * @param array $parameters
615
-     *
616
-     * @return string|null Raw Provider API response, or null if we cannot refresh
617
-     * @throws \Hybridauth\Exception\HttpClientFailureException
618
-     * @throws \Hybridauth\Exception\HttpRequestFailedException
619
-     * @throws InvalidAccessTokenException
620
-     */
621
-    public function refreshAccessToken($parameters = [])
622
-    {
623
-        $this->tokenRefreshParameters = !empty($parameters)
624
-            ? $parameters
625
-            : $this->tokenRefreshParameters;
626
-
627
-        if (!$this->isRefreshTokenAvailable()) {
628
-            return null;
629
-        }
630
-
631
-        $response = $this->httpClient->request(
632
-            $this->accessTokenUrl,
633
-            $this->tokenRefreshMethod,
634
-            $this->tokenRefreshParameters,
635
-            $this->tokenRefreshHeaders
636
-        );
637
-
638
-        $this->validateApiResponse('Unable to refresh the access token');
639
-
640
-        $this->validateRefreshAccessToken($response);
641
-
642
-        return $response;
643
-    }
644
-
645
-    /**
646
-     * Check whether access token has expired
647
-     *
648
-     * @param int|null $time
649
-     * @return bool|null
650
-     */
651
-    public function hasAccessTokenExpired($time = null)
652
-    {
653
-        if ($time === null) {
654
-            $time = time();
655
-        }
656
-
657
-        $expires_at = $this->getStoredData('expires_at');
658
-        if (!$expires_at) {
659
-            return null;
660
-        }
661
-
662
-        return $expires_at <= $time;
663
-    }
664
-
665
-    /**
666
-     * Validate Refresh Access Token Request
667
-     *
668
-     * RFC6749: If valid and authorized, the authorization server issues an
669
-     * access token as described in Section 5.1.  If the request failed
670
-     * verification or is invalid, the authorization server returns an error
671
-     * response as described in Section 5.2.
672
-     *
673
-     * http://tools.ietf.org/html/rfc6749#section-6
674
-     * http://tools.ietf.org/html/rfc6749#section-5.1
675
-     * http://tools.ietf.org/html/rfc6749#section-5.2
676
-     *
677
-     * This method simply use validateAccessTokenExchange(), however sub
678
-     * classes may redefine it when necessary.
679
-     *
680
-     * @param $response
681
-     *
682
-     * @return \Hybridauth\Data\Collection
683
-     * @throws InvalidAccessTokenException
684
-     */
685
-    protected function validateRefreshAccessToken($response)
686
-    {
687
-        return $this->validateAccessTokenExchange($response);
688
-    }
689
-
690
-    /**
691
-     * Send a signed request to provider API
692
-     *
693
-     * RFC6749: Accessing Protected Resources: The client accesses protected
694
-     * resources by presenting the access token to the resource server. The
695
-     * resource server MUST validate the access token and ensure that it has
696
-     * not expired and that its scope covers the requested resource.
697
-     *
698
-     * Note: Since the specifics of error responses is beyond the scope of
699
-     * RFC6749 and OAuth specifications, Hybridauth will consider any HTTP
700
-     * status code that is different than '200 OK' as an ERROR.
701
-     *
702
-     * http://tools.ietf.org/html/rfc6749#section-7
703
-     *
704
-     * @param string $url
705
-     * @param string $method
706
-     * @param array $parameters
707
-     * @param array $headers
708
-     * @param bool $multipart
709
-     *
710
-     * @return mixed
711
-     * @throws \Hybridauth\Exception\HttpClientFailureException
712
-     * @throws \Hybridauth\Exception\HttpRequestFailedException
713
-     * @throws InvalidAccessTokenException
714
-     */
715
-    public function apiRequest($url, $method = 'GET', $parameters = [], $headers = [], $multipart = false)
716
-    {
717
-        // refresh tokens if needed
718
-        $this->maintainToken();
719
-        if ($this->hasAccessTokenExpired() === true) {
720
-            $this->refreshAccessToken();
721
-        }
722
-
723
-        if (strrpos($url, 'http://') !== 0 && strrpos($url, 'https://') !== 0) {
724
-            $url = rtrim($this->apiBaseUrl, '/') . '/' . ltrim($url, '/');
725
-        }
726
-
727
-        $parameters = array_replace($this->apiRequestParameters, (array)$parameters);
728
-        $headers = array_replace($this->apiRequestHeaders, (array)$headers);
729
-
730
-        $response = $this->httpClient->request(
731
-            $url,
732
-            $method,     // HTTP Request Method. Defaults to GET.
733
-            $parameters, // Request Parameters
734
-            $headers,    // Request Headers
735
-            $multipart   // Is request multipart
736
-        );
737
-
738
-        $this->validateApiResponse('Signed API request to ' . $url . ' has returned an error');
739
-
740
-        $response = (new Data\Parser())->parse($response);
741
-
742
-        return $response;
743
-    }
264
+		if ($this->config->exists('supportRequestState')) {
265
+			$this->supportRequestState = $this->config->get('supportRequestState');
266
+		}
267
+
268
+		$this->setCallback($this->config->get('callback'));
269
+		$this->setApiEndpoints($this->config->get('endpoints'));
270
+	}
271
+
272
+	/**
273
+	 * {@inheritdoc}
274
+	 */
275
+	protected function initialize()
276
+	{
277
+		$this->AuthorizeUrlParameters = [
278
+			'response_type' => 'code',
279
+			'client_id' => $this->clientId,
280
+			'redirect_uri' => $this->callback,
281
+			'scope' => $this->scope,
282
+		];
283
+
284
+		$this->tokenExchangeParameters = [
285
+			'client_id' => $this->clientId,
286
+			'client_secret' => $this->clientSecret,
287
+			'grant_type' => 'authorization_code',
288
+			'redirect_uri' => $this->callback
289
+		];
290
+
291
+		$refreshToken = $this->getStoredData('refresh_token');
292
+		if (!empty($refreshToken)) {
293
+			$this->tokenRefreshParameters = [
294
+				'grant_type' => 'refresh_token',
295
+				'refresh_token' => $refreshToken,
296
+			];
297
+		}
298
+
299
+		$this->apiRequestHeaders = [
300
+			'Authorization' => 'Bearer ' . $this->getStoredData('access_token')
301
+		];
302
+	}
303
+
304
+	/**
305
+	 * {@inheritdoc}
306
+	 */
307
+	public function authenticate()
308
+	{
309
+		$this->logger->info(sprintf('%s::authenticate()', get_class($this)));
310
+
311
+		if ($this->isConnected()) {
312
+			return true;
313
+		}
314
+
315
+		try {
316
+			$this->authenticateCheckError();
317
+
318
+			$code = filter_input($_SERVER['REQUEST_METHOD'] === 'POST' ? INPUT_POST : INPUT_GET, 'code');
319
+
320
+			if (empty($code)) {
321
+				$this->authenticateBegin();
322
+			} else {
323
+				$this->authenticateFinish();
324
+			}
325
+		} catch (Exception $e) {
326
+			$this->clearStoredData();
327
+
328
+			throw $e;
329
+		}
330
+
331
+		return null;
332
+	}
333
+
334
+	/**
335
+	 * {@inheritdoc}
336
+	 */
337
+	public function isConnected()
338
+	{
339
+		if ((bool)$this->getStoredData('access_token')) {
340
+			return (!$this->hasAccessTokenExpired() || $this->isRefreshTokenAvailable());
341
+		}
342
+		return false;
343
+	}
344
+
345
+	/**
346
+	 * If we can use a refresh token, then an expired token does not stop us being connected.
347
+	 *
348
+	 * @return bool
349
+	 */
350
+	public function isRefreshTokenAvailable()
351
+	{
352
+		return is_array($this->tokenRefreshParameters);
353
+	}
354
+
355
+	/**
356
+	 * Authorization Request Error Response
357
+	 *
358
+	 * RFC6749: If the request fails due to a missing, invalid, or mismatching
359
+	 * redirection URI, or if the client identifier is missing or invalid,
360
+	 * the authorization server SHOULD inform the resource owner of the error.
361
+	 *
362
+	 * http://tools.ietf.org/html/rfc6749#section-4.1.2.1
363
+	 *
364
+	 * @throws \Hybridauth\Exception\InvalidAuthorizationCodeException
365
+	 * @throws \Hybridauth\Exception\AuthorizationDeniedException
366
+	 */
367
+	protected function authenticateCheckError()
368
+	{
369
+		$error = filter_input(INPUT_GET, 'error', FILTER_SANITIZE_SPECIAL_CHARS);
370
+
371
+		if (!empty($error)) {
372
+			$error_description = filter_input(INPUT_GET, 'error_description', FILTER_SANITIZE_SPECIAL_CHARS);
373
+			$error_uri = filter_input(INPUT_GET, 'error_uri', FILTER_SANITIZE_SPECIAL_CHARS);
374
+
375
+			$collated_error = sprintf('Provider returned an error: %s %s %s', $error, $error_description, $error_uri);
376
+
377
+			if ($error == 'access_denied') {
378
+				throw new AuthorizationDeniedException($collated_error);
379
+			}
380
+
381
+			throw new InvalidAuthorizationCodeException($collated_error);
382
+		}
383
+	}
384
+
385
+	/**
386
+	 * Initiate the authorization protocol
387
+	 *
388
+	 * Build Authorization URL for Authorization Request and redirect the user-agent to the
389
+	 * Authorization Server.
390
+	 */
391
+	protected function authenticateBegin()
392
+	{
393
+		$authUrl = $this->getAuthorizeUrl();
394
+
395
+		$this->logger->debug(sprintf('%s::authenticateBegin(), redirecting user to:', get_class($this)), [$authUrl]);
396
+
397
+		HttpClient\Util::redirect($authUrl);
398
+	}
399
+
400
+	/**
401
+	 * Finalize the authorization process
402
+	 *
403
+	 * @throws \Hybridauth\Exception\HttpClientFailureException
404
+	 * @throws \Hybridauth\Exception\HttpRequestFailedException
405
+	 * @throws InvalidAccessTokenException
406
+	 * @throws InvalidAuthorizationStateException
407
+	 */
408
+	protected function authenticateFinish()
409
+	{
410
+		$this->logger->debug(
411
+			sprintf('%s::authenticateFinish(), callback url:', get_class($this)),
412
+			[HttpClient\Util::getCurrentUrl(true)]
413
+		);
414
+
415
+		$state = filter_input($_SERVER['REQUEST_METHOD'] === 'POST' ? INPUT_POST : INPUT_GET, 'state');
416
+		$code = filter_input($_SERVER['REQUEST_METHOD'] === 'POST' ? INPUT_POST : INPUT_GET, 'code');
417
+
418
+		/**
419
+		 * Authorization Request State
420
+		 *
421
+		 * RFC6749: state : RECOMMENDED. An opaque value used by the client to maintain
422
+		 * state between the request and callback. The authorization server includes
423
+		 * this value when redirecting the user-agent back to the client.
424
+		 *
425
+		 * http://tools.ietf.org/html/rfc6749#section-4.1.1
426
+		 */
427
+		if ($this->supportRequestState
428
+			&& (!$state || $this->getStoredData('authorization_state') != $state)
429
+		) {
430
+			$this->deleteStoredData('authorization_state');
431
+			throw new InvalidAuthorizationStateException(
432
+				'The authorization state [state=' . substr(htmlentities($state), 0, 100) . '] '
433
+				. 'of this page is either invalid or has already been consumed.'
434
+			);
435
+		}
436
+
437
+		/**
438
+		 * Authorization Request Code
439
+		 *
440
+		 * RFC6749: If the resource owner grants the access request, the authorization
441
+		 * server issues an authorization code and delivers it to the client:
442
+		 *
443
+		 * http://tools.ietf.org/html/rfc6749#section-4.1.2
444
+		 */
445
+		$response = $this->exchangeCodeForAccessToken($code);
446
+
447
+		$this->validateAccessTokenExchange($response);
448
+
449
+		$this->initialize();
450
+	}
451
+
452
+	/**
453
+	 * Build Authorization URL for Authorization Request
454
+	 *
455
+	 * RFC6749: The client constructs the request URI by adding the following
456
+	 * $parameters to the query component of the authorization endpoint URI:
457
+	 *
458
+	 *    - response_type  REQUIRED. Value MUST be set to "code".
459
+	 *    - client_id      REQUIRED.
460
+	 *    - redirect_uri   OPTIONAL.
461
+	 *    - scope          OPTIONAL.
462
+	 *    - state          RECOMMENDED.
463
+	 *
464
+	 * http://tools.ietf.org/html/rfc6749#section-4.1.1
465
+	 *
466
+	 * Sub classes may redefine this method when necessary.
467
+	 *
468
+	 * @param array $parameters
469
+	 *
470
+	 * @return string Authorization URL
471
+	 */
472
+	protected function getAuthorizeUrl($parameters = [])
473
+	{
474
+		$this->AuthorizeUrlParameters = !empty($parameters)
475
+			? $parameters
476
+			: array_replace(
477
+				(array)$this->AuthorizeUrlParameters,
478
+				(array)$this->config->get('authorize_url_parameters')
479
+			);
480
+
481
+		if ($this->supportRequestState) {
482
+			if (!isset($this->AuthorizeUrlParameters['state'])) {
483
+				$this->AuthorizeUrlParameters['state'] = 'HA-' . str_shuffle('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890');
484
+			}
485
+
486
+			$this->storeData('authorization_state', $this->AuthorizeUrlParameters['state']);
487
+		}
488
+
489
+		$queryParams = http_build_query($this->AuthorizeUrlParameters, '', '&', $this->AuthorizeUrlParametersEncType);
490
+		return $this->authorizeUrl . '?' . $queryParams;
491
+	}
492
+
493
+	/**
494
+	 * Access Token Request
495
+	 *
496
+	 * This method will exchange the received $code in loginFinish() with an Access Token.
497
+	 *
498
+	 * RFC6749: The client makes a request to the token endpoint by sending the
499
+	 * following parameters using the "application/x-www-form-urlencoded"
500
+	 * with a character encoding of UTF-8 in the HTTP request entity-body:
501
+	 *
502
+	 *    - grant_type    REQUIRED. Value MUST be set to "authorization_code".
503
+	 *    - code          REQUIRED. The authorization code received from the authorization server.
504
+	 *    - redirect_uri  REQUIRED.
505
+	 *    - client_id     REQUIRED.
506
+	 *
507
+	 * http://tools.ietf.org/html/rfc6749#section-4.1.3
508
+	 *
509
+	 * @param string $code
510
+	 *
511
+	 * @return string Raw Provider API response
512
+	 * @throws \Hybridauth\Exception\HttpClientFailureException
513
+	 * @throws \Hybridauth\Exception\HttpRequestFailedException
514
+	 */
515
+	protected function exchangeCodeForAccessToken($code)
516
+	{
517
+		$this->tokenExchangeParameters['code'] = $code;
518
+
519
+		$response = $this->httpClient->request(
520
+			$this->accessTokenUrl,
521
+			$this->tokenExchangeMethod,
522
+			$this->tokenExchangeParameters,
523
+			$this->tokenExchangeHeaders
524
+		);
525
+
526
+		$this->validateApiResponse('Unable to exchange code for API access token');
527
+
528
+		return $response;
529
+	}
530
+
531
+	/**
532
+	 * Validate Access Token Response
533
+	 *
534
+	 * RFC6749: If the access token request is valid and authorized, the
535
+	 * authorization server issues an access token and optional refresh token.
536
+	 * If the request client authentication failed or is invalid, the authorization
537
+	 * server returns an error response as described in Section 5.2.
538
+	 *
539
+	 * Example of a successful response:
540
+	 *
541
+	 *  HTTP/1.1 200 OK
542
+	 *  Content-Type: application/json;charset=UTF-8
543
+	 *  Cache-Control: no-store
544
+	 *  Pragma: no-cache
545
+	 *
546
+	 *  {
547
+	 *      "access_token":"2YotnFZFEjr1zCsicMWpAA",
548
+	 *      "token_type":"example",
549
+	 *      "expires_in":3600,
550
+	 *      "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
551
+	 *      "example_parameter":"example_value"
552
+	 *  }
553
+	 *
554
+	 * http://tools.ietf.org/html/rfc6749#section-4.1.4
555
+	 *
556
+	 * This method uses Data_Parser to attempt to decodes the raw $response (usually JSON)
557
+	 * into a data collection.
558
+	 *
559
+	 * @param string $response
560
+	 *
561
+	 * @return \Hybridauth\Data\Collection
562
+	 * @throws InvalidAccessTokenException
563
+	 */
564
+	protected function validateAccessTokenExchange($response)
565
+	{
566
+		$data = (new Data\Parser())->parse($response);
567
+
568
+		$collection = new Data\Collection($data);
569
+
570
+		if (!$collection->exists('access_token')) {
571
+			throw new InvalidAccessTokenException(
572
+				'Provider returned no access_token: ' . htmlentities($response)
573
+			);
574
+		}
575
+
576
+		$this->storeData('access_token', $collection->get('access_token'));
577
+		$this->storeData('token_type', $collection->get('token_type'));
578
+
579
+		if ($collection->get('refresh_token')) {
580
+			$this->storeData('refresh_token', $collection->get('refresh_token'));
581
+		}
582
+
583
+		// calculate when the access token expire
584
+		if ($collection->exists('expires_in')) {
585
+			$expires_at = time() + (int)$collection->get('expires_in');
586
+
587
+			$this->storeData('expires_in', $collection->get('expires_in'));
588
+			$this->storeData('expires_at', $expires_at);
589
+		}
590
+
591
+		$this->deleteStoredData('authorization_state');
592
+
593
+		$this->initialize();
594
+
595
+		return $collection;
596
+	}
597
+
598
+	/**
599
+	 * Refreshing an Access Token
600
+	 *
601
+	 * RFC6749: If the authorization server issued a refresh token to the
602
+	 * client, the client makes a refresh request to the token endpoint by
603
+	 * adding the following parameters ... in the HTTP request entity-body:
604
+	 *
605
+	 *    - grant_type     REQUIRED. Value MUST be set to "refresh_token".
606
+	 *    - refresh_token  REQUIRED. The refresh token issued to the client.
607
+	 *    - scope          OPTIONAL.
608
+	 *
609
+	 * http://tools.ietf.org/html/rfc6749#section-6
610
+	 *
611
+	 * This method is similar to exchangeCodeForAccessToken(). The only
612
+	 * difference is here we exchange refresh_token for a new access_token.
613
+	 *
614
+	 * @param array $parameters
615
+	 *
616
+	 * @return string|null Raw Provider API response, or null if we cannot refresh
617
+	 * @throws \Hybridauth\Exception\HttpClientFailureException
618
+	 * @throws \Hybridauth\Exception\HttpRequestFailedException
619
+	 * @throws InvalidAccessTokenException
620
+	 */
621
+	public function refreshAccessToken($parameters = [])
622
+	{
623
+		$this->tokenRefreshParameters = !empty($parameters)
624
+			? $parameters
625
+			: $this->tokenRefreshParameters;
626
+
627
+		if (!$this->isRefreshTokenAvailable()) {
628
+			return null;
629
+		}
630
+
631
+		$response = $this->httpClient->request(
632
+			$this->accessTokenUrl,
633
+			$this->tokenRefreshMethod,
634
+			$this->tokenRefreshParameters,
635
+			$this->tokenRefreshHeaders
636
+		);
637
+
638
+		$this->validateApiResponse('Unable to refresh the access token');
639
+
640
+		$this->validateRefreshAccessToken($response);
641
+
642
+		return $response;
643
+	}
644
+
645
+	/**
646
+	 * Check whether access token has expired
647
+	 *
648
+	 * @param int|null $time
649
+	 * @return bool|null
650
+	 */
651
+	public function hasAccessTokenExpired($time = null)
652
+	{
653
+		if ($time === null) {
654
+			$time = time();
655
+		}
656
+
657
+		$expires_at = $this->getStoredData('expires_at');
658
+		if (!$expires_at) {
659
+			return null;
660
+		}
661
+
662
+		return $expires_at <= $time;
663
+	}
664
+
665
+	/**
666
+	 * Validate Refresh Access Token Request
667
+	 *
668
+	 * RFC6749: If valid and authorized, the authorization server issues an
669
+	 * access token as described in Section 5.1.  If the request failed
670
+	 * verification or is invalid, the authorization server returns an error
671
+	 * response as described in Section 5.2.
672
+	 *
673
+	 * http://tools.ietf.org/html/rfc6749#section-6
674
+	 * http://tools.ietf.org/html/rfc6749#section-5.1
675
+	 * http://tools.ietf.org/html/rfc6749#section-5.2
676
+	 *
677
+	 * This method simply use validateAccessTokenExchange(), however sub
678
+	 * classes may redefine it when necessary.
679
+	 *
680
+	 * @param $response
681
+	 *
682
+	 * @return \Hybridauth\Data\Collection
683
+	 * @throws InvalidAccessTokenException
684
+	 */
685
+	protected function validateRefreshAccessToken($response)
686
+	{
687
+		return $this->validateAccessTokenExchange($response);
688
+	}
689
+
690
+	/**
691
+	 * Send a signed request to provider API
692
+	 *
693
+	 * RFC6749: Accessing Protected Resources: The client accesses protected
694
+	 * resources by presenting the access token to the resource server. The
695
+	 * resource server MUST validate the access token and ensure that it has
696
+	 * not expired and that its scope covers the requested resource.
697
+	 *
698
+	 * Note: Since the specifics of error responses is beyond the scope of
699
+	 * RFC6749 and OAuth specifications, Hybridauth will consider any HTTP
700
+	 * status code that is different than '200 OK' as an ERROR.
701
+	 *
702
+	 * http://tools.ietf.org/html/rfc6749#section-7
703
+	 *
704
+	 * @param string $url
705
+	 * @param string $method
706
+	 * @param array $parameters
707
+	 * @param array $headers
708
+	 * @param bool $multipart
709
+	 *
710
+	 * @return mixed
711
+	 * @throws \Hybridauth\Exception\HttpClientFailureException
712
+	 * @throws \Hybridauth\Exception\HttpRequestFailedException
713
+	 * @throws InvalidAccessTokenException
714
+	 */
715
+	public function apiRequest($url, $method = 'GET', $parameters = [], $headers = [], $multipart = false)
716
+	{
717
+		// refresh tokens if needed
718
+		$this->maintainToken();
719
+		if ($this->hasAccessTokenExpired() === true) {
720
+			$this->refreshAccessToken();
721
+		}
722
+
723
+		if (strrpos($url, 'http://') !== 0 && strrpos($url, 'https://') !== 0) {
724
+			$url = rtrim($this->apiBaseUrl, '/') . '/' . ltrim($url, '/');
725
+		}
726
+
727
+		$parameters = array_replace($this->apiRequestParameters, (array)$parameters);
728
+		$headers = array_replace($this->apiRequestHeaders, (array)$headers);
729
+
730
+		$response = $this->httpClient->request(
731
+			$url,
732
+			$method,     // HTTP Request Method. Defaults to GET.
733
+			$parameters, // Request Parameters
734
+			$headers,    // Request Headers
735
+			$multipart   // Is request multipart
736
+		);
737
+
738
+		$this->validateApiResponse('Signed API request to ' . $url . ' has returned an error');
739
+
740
+		$response = (new Data\Parser())->parse($response);
741
+
742
+		return $response;
743
+	}
744 744
 }
Please login to merge, or discard this patch.
Spacing   +27 added lines, -27 removed lines patch added patch discarded remove patch
@@ -249,9 +249,9 @@  discard block
 block discarded – undo
249 249
         $this->clientId = $this->config->filter('keys')->get('id') ?: $this->config->filter('keys')->get('key');
250 250
         $this->clientSecret = $this->config->filter('keys')->get('secret');
251 251
 
252
-        if (!$this->clientId || !$this->clientSecret) {
252
+        if ( ! $this->clientId || ! $this->clientSecret) {
253 253
             throw new InvalidApplicationCredentialsException(
254
-                'Your application id is required in order to connect to ' . $this->providerId
254
+                'Your application id is required in order to connect to '.$this->providerId
255 255
             );
256 256
         }
257 257
 
@@ -289,7 +289,7 @@  discard block
 block discarded – undo
289 289
         ];
290 290
 
291 291
         $refreshToken = $this->getStoredData('refresh_token');
292
-        if (!empty($refreshToken)) {
292
+        if ( ! empty($refreshToken)) {
293 293
             $this->tokenRefreshParameters = [
294 294
                 'grant_type' => 'refresh_token',
295 295
                 'refresh_token' => $refreshToken,
@@ -297,7 +297,7 @@  discard block
 block discarded – undo
297 297
         }
298 298
 
299 299
         $this->apiRequestHeaders = [
300
-            'Authorization' => 'Bearer ' . $this->getStoredData('access_token')
300
+            'Authorization' => 'Bearer '.$this->getStoredData('access_token')
301 301
         ];
302 302
     }
303 303
 
@@ -336,8 +336,8 @@  discard block
 block discarded – undo
336 336
      */
337 337
     public function isConnected()
338 338
     {
339
-        if ((bool)$this->getStoredData('access_token')) {
340
-            return (!$this->hasAccessTokenExpired() || $this->isRefreshTokenAvailable());
339
+        if ((bool) $this->getStoredData('access_token')) {
340
+            return ( ! $this->hasAccessTokenExpired() || $this->isRefreshTokenAvailable());
341 341
         }
342 342
         return false;
343 343
     }
@@ -368,7 +368,7 @@  discard block
 block discarded – undo
368 368
     {
369 369
         $error = filter_input(INPUT_GET, 'error', FILTER_SANITIZE_SPECIAL_CHARS);
370 370
 
371
-        if (!empty($error)) {
371
+        if ( ! empty($error)) {
372 372
             $error_description = filter_input(INPUT_GET, 'error_description', FILTER_SANITIZE_SPECIAL_CHARS);
373 373
             $error_uri = filter_input(INPUT_GET, 'error_uri', FILTER_SANITIZE_SPECIAL_CHARS);
374 374
 
@@ -425,11 +425,11 @@  discard block
 block discarded – undo
425 425
          * http://tools.ietf.org/html/rfc6749#section-4.1.1
426 426
          */
427 427
         if ($this->supportRequestState
428
-            && (!$state || $this->getStoredData('authorization_state') != $state)
428
+            && ( ! $state || $this->getStoredData('authorization_state') != $state)
429 429
         ) {
430 430
             $this->deleteStoredData('authorization_state');
431 431
             throw new InvalidAuthorizationStateException(
432
-                'The authorization state [state=' . substr(htmlentities($state), 0, 100) . '] '
432
+                'The authorization state [state='.substr(htmlentities($state), 0, 100).'] '
433 433
                 . 'of this page is either invalid or has already been consumed.'
434 434
             );
435 435
         }
@@ -471,23 +471,23 @@  discard block
 block discarded – undo
471 471
      */
472 472
     protected function getAuthorizeUrl($parameters = [])
473 473
     {
474
-        $this->AuthorizeUrlParameters = !empty($parameters)
474
+        $this->AuthorizeUrlParameters = ! empty($parameters)
475 475
             ? $parameters
476 476
             : array_replace(
477
-                (array)$this->AuthorizeUrlParameters,
478
-                (array)$this->config->get('authorize_url_parameters')
477
+                (array) $this->AuthorizeUrlParameters,
478
+                (array) $this->config->get('authorize_url_parameters')
479 479
             );
480 480
 
481 481
         if ($this->supportRequestState) {
482
-            if (!isset($this->AuthorizeUrlParameters['state'])) {
483
-                $this->AuthorizeUrlParameters['state'] = 'HA-' . str_shuffle('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890');
482
+            if ( ! isset($this->AuthorizeUrlParameters['state'])) {
483
+                $this->AuthorizeUrlParameters['state'] = 'HA-'.str_shuffle('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890');
484 484
             }
485 485
 
486 486
             $this->storeData('authorization_state', $this->AuthorizeUrlParameters['state']);
487 487
         }
488 488
 
489 489
         $queryParams = http_build_query($this->AuthorizeUrlParameters, '', '&', $this->AuthorizeUrlParametersEncType);
490
-        return $this->authorizeUrl . '?' . $queryParams;
490
+        return $this->authorizeUrl.'?'.$queryParams;
491 491
     }
492 492
 
493 493
     /**
@@ -567,9 +567,9 @@  discard block
 block discarded – undo
567 567
 
568 568
         $collection = new Data\Collection($data);
569 569
 
570
-        if (!$collection->exists('access_token')) {
570
+        if ( ! $collection->exists('access_token')) {
571 571
             throw new InvalidAccessTokenException(
572
-                'Provider returned no access_token: ' . htmlentities($response)
572
+                'Provider returned no access_token: '.htmlentities($response)
573 573
             );
574 574
         }
575 575
 
@@ -582,7 +582,7 @@  discard block
 block discarded – undo
582 582
 
583 583
         // calculate when the access token expire
584 584
         if ($collection->exists('expires_in')) {
585
-            $expires_at = time() + (int)$collection->get('expires_in');
585
+            $expires_at = time() + (int) $collection->get('expires_in');
586 586
 
587 587
             $this->storeData('expires_in', $collection->get('expires_in'));
588 588
             $this->storeData('expires_at', $expires_at);
@@ -620,11 +620,11 @@  discard block
 block discarded – undo
620 620
      */
621 621
     public function refreshAccessToken($parameters = [])
622 622
     {
623
-        $this->tokenRefreshParameters = !empty($parameters)
623
+        $this->tokenRefreshParameters = ! empty($parameters)
624 624
             ? $parameters
625 625
             : $this->tokenRefreshParameters;
626 626
 
627
-        if (!$this->isRefreshTokenAvailable()) {
627
+        if ( ! $this->isRefreshTokenAvailable()) {
628 628
             return null;
629 629
         }
630 630
 
@@ -655,7 +655,7 @@  discard block
 block discarded – undo
655 655
         }
656 656
 
657 657
         $expires_at = $this->getStoredData('expires_at');
658
-        if (!$expires_at) {
658
+        if ( ! $expires_at) {
659 659
             return null;
660 660
         }
661 661
 
@@ -721,21 +721,21 @@  discard block
 block discarded – undo
721 721
         }
722 722
 
723 723
         if (strrpos($url, 'http://') !== 0 && strrpos($url, 'https://') !== 0) {
724
-            $url = rtrim($this->apiBaseUrl, '/') . '/' . ltrim($url, '/');
724
+            $url = rtrim($this->apiBaseUrl, '/').'/'.ltrim($url, '/');
725 725
         }
726 726
 
727
-        $parameters = array_replace($this->apiRequestParameters, (array)$parameters);
728
-        $headers = array_replace($this->apiRequestHeaders, (array)$headers);
727
+        $parameters = array_replace($this->apiRequestParameters, (array) $parameters);
728
+        $headers = array_replace($this->apiRequestHeaders, (array) $headers);
729 729
 
730 730
         $response = $this->httpClient->request(
731 731
             $url,
732
-            $method,     // HTTP Request Method. Defaults to GET.
732
+            $method, // HTTP Request Method. Defaults to GET.
733 733
             $parameters, // Request Parameters
734
-            $headers,    // Request Headers
734
+            $headers, // Request Headers
735 735
             $multipart   // Is request multipart
736 736
         );
737 737
 
738
-        $this->validateApiResponse('Signed API request to ' . $url . ' has returned an error');
738
+        $this->validateApiResponse('Signed API request to '.$url.' has returned an error');
739 739
 
740 740
         $response = (new Data\Parser())->parse($response);
741 741
 
Please login to merge, or discard this patch.