AbstractProvider::hasInvalidState()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3.072

Importance

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