Completed
Push — master ( 761121...8bda57 )
by Carlos
08:56
created

AbstractProvider::hasInvalidState()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 10
rs 9.4286
cc 3
eloc 5
nc 3
nop 0
1
<?php
2
3
/*
4
 * This file is part of the overtrue/socialite.
5
 *
6
 * (c) overtrue <[email protected]>
7
 *
8
 * This source file is subject to the MIT license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace Overtrue\Socialite\Providers;
13
14
use GuzzleHttp\Client;
15
use GuzzleHttp\ClientInterface;
16
use Overtrue\Socialite\AccessToken;
17
use Overtrue\Socialite\AccessTokenInterface;
18
use Overtrue\Socialite\InvalidStateException;
19
use Overtrue\Socialite\ProviderInterface;
20
use Symfony\Component\HttpFoundation\RedirectResponse;
21
use Symfony\Component\HttpFoundation\Request;
22
23
/**
24
 * Class AbstractProvider.
25
 */
26
abstract class AbstractProvider implements ProviderInterface
27
{
28
    /**
29
     * The HTTP request instance.
30
     *
31
     * @var Request
32
     */
33
    protected $request;
34
35
    /**
36
     * The client ID.
37
     *
38
     * @var string
39
     */
40
    protected $clientId;
41
42
    /**
43
     * The client secret.
44
     *
45
     * @var string
46
     */
47
    protected $clientSecret;
48
49
    /**
50
     * The redirect URL.
51
     *
52
     * @var string
53
     */
54
    protected $redirectUrl;
55
56
    /**
57
     * The custom parameters to be sent with the request.
58
     *
59
     * @var array
60
     */
61
    protected $parameters = [];
62
63
    /**
64
     * The scopes being requested.
65
     *
66
     * @var array
67
     */
68
    protected $scopes = [];
69
70
    /**
71
     * The separating character for the requested scopes.
72
     *
73
     * @var string
74
     */
75
    protected $scopeSeparator = ',';
76
77
    /**
78
     * The type of the encoding in the query.
79
     *
80
     * @var int Can be either PHP_QUERY_RFC3986 or PHP_QUERY_RFC1738.
81
     */
82
    protected $encodingType = PHP_QUERY_RFC1738;
83
84
    /**
85
     * Indicates if the session state should be utilized.
86
     *
87
     * @var bool
88
     */
89
    protected $stateless = false;
90
91
    /**
92
     * Create a new provider instance.
93
     *
94
     * @param Request     $request
95
     * @param string      $clientId
96
     * @param string      $clientSecret
97
     * @param string|null $redirectUrl
98
     */
99
    public function __construct(Request $request, $clientId, $clientSecret, $redirectUrl = null)
0 ignored issues
show
Bug introduced by
You have injected the Request via parameter $request. This is generally not recommended as there might be multiple instances during a request cycle (f.e. when using sub-requests). Instead, it is recommended to inject the RequestStack and retrieve the current request each time you need it via getCurrentRequest().
Loading history...
100
    {
101
        $this->request      = $request;
102
        $this->clientId     = $clientId;
103
        $this->clientSecret = $clientSecret;
104
        $this->redirectUrl  = $redirectUrl;
105
    }
106
107
    /**
108
     * Get the authentication URL for the provider.
109
     *
110
     * @param string $state
111
     *
112
     * @return string
113
     */
114
    abstract protected function getAuthUrl($state);
115
116
    /**
117
     * Get the token URL for the provider.
118
     *
119
     * @return string
120
     */
121
    abstract protected function getTokenUrl();
122
123
    /**
124
     * Get the raw user for the given access token.
125
     *
126
     * @param \Overtrue\Socialite\AccessTokenInterface $token
127
     *
128
     * @return array
129
     */
130
    abstract protected function getUserByToken(AccessTokenInterface $token);
131
132
    /**
133
     * Map the raw user array to a Socialite User instance.
134
     *
135
     * @param array $user
136
     *
137
     * @return \Overtrue\Socialite\User
138
     */
139
    abstract protected function mapUserToObject(array $user);
140
141
    /**
142
     * Redirect the user of the application to the provider's authentication screen.
143
     *
144
     * @param string $redirectUrl
145
     *
146
     * @return \Symfony\Component\HttpFoundation\RedirectResponse
147
     */
148
    public function redirect($redirectUrl = null)
149
    {
150
        $state = null;
151
152
        if (!is_null($redirectUrl)) {
153
            $this->redirectUrl = $redirectUrl;
154
        }
155
156
        if ($this->usesState()) {
157
            $this->request->getSession()->set('state', $state = md5(time()));
158
        }
159
160
        return new RedirectResponse($this->getAuthUrl($state));
161
    }
162
163
    /**
164
     * Get the authentication URL for the provider.
165
     *
166
     * @param string $url
167
     * @param string $state
168
     *
169
     * @return string
170
     */
171
    protected function buildAuthUrlFromBase($url, $state)
172
    {
173
        return $url.'?'.http_build_query($this->getCodeFields($state), '', '&', $this->encodingType);
174
    }
175
176
    /**
177
     * Get the GET parameters for the code request.
178
     *
179
     * @param string|null $state
180
     *
181
     * @return array
182
     */
183
    protected function getCodeFields($state = null)
184
    {
185
        $fields = [
186
            'client_id'     => $this->clientId,
187
            'redirect_uri'  => $this->redirectUrl,
188
            'scope'         => $this->formatScopes($this->scopes, $this->scopeSeparator),
189
            'response_type' => 'code',
190
        ];
191
192
        if ($this->usesState()) {
193
            $fields['state'] = $state;
194
        }
195
196
        return array_merge($fields, $this->parameters);
197
    }
198
199
    /**
200
     * Format the given scopes.
201
     *
202
     * @param array  $scopes
203
     * @param string $scopeSeparator
204
     *
205
     * @return string
206
     */
207
    protected function formatScopes(array $scopes, $scopeSeparator)
208
    {
209
        return implode($scopeSeparator, $scopes);
210
    }
211
212
    /**
213
     * {@inheritdoc}
214
     */
215
    public function user()
216
    {
217
        if ($this->hasInvalidState()) {
218
            throw new InvalidStateException();
219
        }
220
221
        $user = $this->getUserByToken(
222
            $token = $this->getAccessToken($this->getCode())
223
        );
224
225
        $user = $this->mapUserToObject($user)->merge(['original' => $user]);
226
227
        return $user->setToken($token);
228
    }
229
230
    /**
231
     * Set redirect url.
232
     *
233
     * @param string $redirectUrl
234
     *
235
     * @return $this
236
     */
237
    public function setRedirectUrl($redirectUrl)
238
    {
239
        $this->redirectUrl = $redirectUrl;
240
241
        return $this;
242
    }
243
244
    /**
245
     * Set redirect url.
246
     *
247
     * @param string $redirectUrl
248
     *
249
     * @return $this
250
     */
251
    public function withRedirectUrl($redirectUrl)
252
    {
253
        $this->redirectUrl = $redirectUrl;
254
255
        return $this;
256
    }
257
258
    /**
259
     * Return the redirect url.
260
     *
261
     * @return string
262
     */
263
    public function getRedirectUrl()
264
    {
265
        return $this->redirectUrl;
266
    }
267
268
    /**
269
     * Determine if the current request / session has a mismatching "state".
270
     *
271
     * @return bool
272
     */
273
    protected function hasInvalidState()
274
    {
275
        if ($this->isStateless()) {
276
            return false;
277
        }
278
279
        $state = $this->request->getSession()->get('state');
280
281
        return !(strlen($state) > 0 && $this->request->get('state') === $state);
282
    }
283
284
    /**
285
     * Get the access token for the given code.
286
     *
287
     * @param string $code
288
     *
289
     * @return \Overtrue\Socialite\AccessToken
290
     */
291
    public function getAccessToken($code)
292
    {
293
        $postKey = (version_compare(ClientInterface::VERSION, '6') === 1) ? 'form_params' : 'body';
294
295
        $response = $this->getHttpClient()->post($this->getTokenUrl(), [
296
            'headers' => ['Accept' => 'application/json'],
297
            $postKey  => $this->getTokenFields($code),
298
        ]);
299
300
        return $this->parseAccessToken($response->getBody());
301
    }
302
303
    /**
304
     * Get the POST fields for the token request.
305
     *
306
     * @param string $code
307
     *
308
     * @return array
309
     */
310
    protected function getTokenFields($code)
311
    {
312
        return [
313
            'client_id'     => $this->clientId,
314
            'client_secret' => $this->clientSecret,
315
            'code'          => $code,
316
            'redirect_uri'  => $this->redirectUrl,
317
        ];
318
    }
319
320
    /**
321
     * Get the access token from the token response body.
322
     *
323
     * @param \Psr\Http\Message\StreamInterface $body
324
     *
325
     * @return \Overtrue\Socialite\AccessToken
326
     */
327
    protected function parseAccessToken($body)
328
    {
329
        return new AccessToken((array) json_decode($body, true));
330
    }
331
332
    /**
333
     * Get the code from the request.
334
     *
335
     * @return string
336
     */
337
    protected function getCode()
338
    {
339
        return $this->request->get('code');
340
    }
341
342
    /**
343
     * Set the scopes of the requested access.
344
     *
345
     * @param array $scopes
346
     *
347
     * @return $this
348
     */
349
    public function scopes(array $scopes)
350
    {
351
        $this->scopes = $scopes;
352
353
        return $this;
354
    }
355
356
    /**
357
     * Get a fresh instance of the Guzzle HTTP client.
358
     *
359
     * @return \GuzzleHttp\Client
360
     */
361
    protected function getHttpClient()
362
    {
363
        return new Client();
364
    }
365
366
    /**
367
     * Set the request instance.
368
     *
369
     * @param Request $request
370
     *
371
     * @return $this
372
     */
373
    public function setRequest(Request $request)
0 ignored issues
show
Bug introduced by
You have injected the Request via parameter $request. This is generally not recommended as there might be multiple instances during a request cycle (f.e. when using sub-requests). Instead, it is recommended to inject the RequestStack and retrieve the current request each time you need it via getCurrentRequest().
Loading history...
374
    {
375
        $this->request = $request;
376
377
        return $this;
378
    }
379
380
    /**
381
     * Determine if the provider is operating with state.
382
     *
383
     * @return bool
384
     */
385
    protected function usesState()
386
    {
387
        return !$this->stateless;
388
    }
389
390
    /**
391
     * Determine if the provider is operating as stateless.
392
     *
393
     * @return bool
394
     */
395
    protected function isStateless()
396
    {
397
        return $this->stateless;
398
    }
399
400
    /**
401
     * Indicates that the provider should operate as stateless.
402
     *
403
     * @return $this
404
     */
405
    public function stateless()
406
    {
407
        $this->stateless = true;
408
409
        return $this;
410
    }
411
412
    /**
413
     * Set the custom parameters of the request.
414
     *
415
     * @param array $parameters
416
     *
417
     * @return $this
418
     */
419
    public function with(array $parameters)
420
    {
421
        $this->parameters = $parameters;
422
423
        return $this;
424
    }
425
426
    /**
427
     * Return array item by key.
428
     *
429
     * @param array  $array
430
     * @param string $key
431
     * @param mixed  $default
432
     *
433
     * @return mixed
434
     */
435
    public function arrayItem(array $array, $key, $default = null)
436
    {
437
        if (is_null($key)) {
438
            return $array;
439
        }
440
441
        if (isset($array[$key])) {
442
            return $array[$key];
443
        }
444
445
        foreach (explode('.', $key) as $segment) {
446
            if (!is_array($array) || !array_key_exists($segment, $array)) {
447
                return $default;
448
            }
449
450
            $array = $array[$segment];
451
        }
452
453
        return $array;
454
    }
455
}
456