Completed
Push — master ( 7acf12...ccecf5 )
by Carlos
04:17 queued 01:16
created

AbstractProvider::mapUserToObject()

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 1
nc 1
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
            $state = sha1(uniqid(mt_rand(1, 1000000), true));
158
            $this->request->getSession()->set('state', $state);
159
        }
160
161
        return new RedirectResponse($this->getAuthUrl($state));
162
    }
163
164
    /**
165
     * Get the authentication URL for the provider.
166
     *
167
     * @param string $url
168
     * @param string $state
169
     *
170
     * @return string
171
     */
172
    protected function buildAuthUrlFromBase($url, $state)
173
    {
174
        return $url.'?'.http_build_query($this->getCodeFields($state), '', '&', $this->encodingType);
175
    }
176
177
    /**
178
     * Get the GET parameters for the code request.
179
     *
180
     * @param string|null $state
181
     *
182
     * @return array
183
     */
184
    protected function getCodeFields($state = null)
185
    {
186
        $fields = array_merge([
187
            'client_id'     => $this->clientId,
188
            'redirect_uri'  => $this->redirectUrl,
189
            'scope'         => $this->formatScopes($this->scopes, $this->scopeSeparator),
190
            'response_type' => 'code',
191
        ], $this->parameters);
192
193
        if ($this->usesState()) {
194
            $fields['state'] = $state;
195
        }
196
197
        return $fields;
198
    }
199
200
    /**
201
     * Format the given scopes.
202
     *
203
     * @param array  $scopes
204
     * @param string $scopeSeparator
205
     *
206
     * @return string
207
     */
208
    protected function formatScopes(array $scopes, $scopeSeparator)
209
    {
210
        return implode($scopeSeparator, $scopes);
211
    }
212
213
    /**
214
     * {@inheritdoc}
215
     */
216
    public function user(AccessTokenInterface $token = null)
217
    {
218
        if (is_null($token) && $this->hasInvalidState()) {
219
            throw new InvalidStateException();
220
        }
221
222
        $token = $token ?: $this->getAccessToken($this->getCode());
223
224
        $user = $this->getUserByToken($token);
225
226
        $user = $this->mapUserToObject($user)->merge(['original' => $user]);
227
228
        return $user->setToken($token);
229
    }
230
231
    /**
232
     * Set redirect url.
233
     *
234
     * @param string $redirectUrl
235
     *
236
     * @return $this
237
     */
238
    public function setRedirectUrl($redirectUrl)
239
    {
240
        $this->redirectUrl = $redirectUrl;
241
242
        return $this;
243
    }
244
245
    /**
246
     * Set redirect url.
247
     *
248
     * @param string $redirectUrl
249
     *
250
     * @return $this
251
     */
252
    public function withRedirectUrl($redirectUrl)
253
    {
254
        $this->redirectUrl = $redirectUrl;
255
256
        return $this;
257
    }
258
259
    /**
260
     * Return the redirect url.
261
     *
262
     * @return string
263
     */
264
    public function getRedirectUrl()
265
    {
266
        return $this->redirectUrl;
267
    }
268
269
    /**
270
     * Determine if the current request / session has a mismatching "state".
271
     *
272
     * @return bool
273
     */
274
    protected function hasInvalidState()
275
    {
276
        if ($this->isStateless()) {
277
            return false;
278
        }
279
280
        $state = $this->request->getSession()->get('state');
281
282
        return !(strlen($state) > 0 && $this->request->get('state') === $state);
283
    }
284
285
    /**
286
     * Get the access token for the given code.
287
     *
288
     * @param string $code
289
     *
290
     * @return \Overtrue\Socialite\AccessToken
291
     */
292
    public function getAccessToken($code)
293
    {
294
        $postKey = (version_compare(ClientInterface::VERSION, '6') === 1) ? 'form_params' : 'body';
295
296
        $response = $this->getHttpClient()->post($this->getTokenUrl(), [
297
            'headers' => ['Accept' => 'application/json'],
298
            $postKey  => $this->getTokenFields($code),
299
        ]);
300
301
        return $this->parseAccessToken($response->getBody());
302
    }
303
304
    /**
305
     * Get the POST fields for the token request.
306
     *
307
     * @param string $code
308
     *
309
     * @return array
310
     */
311
    protected function getTokenFields($code)
312
    {
313
        return [
314
            'client_id'     => $this->clientId,
315
            'client_secret' => $this->clientSecret,
316
            'code'          => $code,
317
            'redirect_uri'  => $this->redirectUrl,
318
        ];
319
    }
320
321
    /**
322
     * Get the access token from the token response body.
323
     *
324
     * @param \Psr\Http\Message\StreamInterface $body
325
     *
326
     * @return \Overtrue\Socialite\AccessToken
327
     */
328
    protected function parseAccessToken($body)
329
    {
330
        return new AccessToken((array) json_decode($body, true));
331
    }
332
333
    /**
334
     * Get the code from the request.
335
     *
336
     * @return string
337
     */
338
    protected function getCode()
339
    {
340
        return $this->request->get('code');
341
    }
342
343
    /**
344
     * Set the scopes of the requested access.
345
     *
346
     * @param array $scopes
347
     *
348
     * @return $this
349
     */
350
    public function scopes(array $scopes)
351
    {
352
        $this->scopes = $scopes;
353
354
        return $this;
355
    }
356
357
    /**
358
     * Get a fresh instance of the Guzzle HTTP client.
359
     *
360
     * @return \GuzzleHttp\Client
361
     */
362
    protected function getHttpClient()
363
    {
364
        return new Client();
365
    }
366
367
    /**
368
     * Set the request instance.
369
     *
370
     * @param Request $request
371
     *
372
     * @return $this
373
     */
374
    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...
375
    {
376
        $this->request = $request;
377
378
        return $this;
379
    }
380
    
381
    /**
382
     * Get the request instance.
383
     *
384
     * @return Request
385
     */
386
    public function getRequest()
387
    {
388
        return $this->request;
389
    }
390
391
    /**
392
     * Determine if the provider is operating with state.
393
     *
394
     * @return bool
395
     */
396
    protected function usesState()
397
    {
398
        return !$this->stateless;
399
    }
400
401
    /**
402
     * Determine if the provider is operating as stateless.
403
     *
404
     * @return bool
405
     */
406
    protected function isStateless()
407
    {
408
        return $this->stateless;
409
    }
410
411
    /**
412
     * Indicates that the provider should operate as stateless.
413
     *
414
     * @return $this
415
     */
416
    public function stateless()
417
    {
418
        $this->stateless = true;
419
420
        return $this;
421
    }
422
423
    /**
424
     * Set the custom parameters of the request.
425
     *
426
     * @param array $parameters
427
     *
428
     * @return $this
429
     */
430
    public function with(array $parameters)
431
    {
432
        $this->parameters = $parameters;
433
434
        return $this;
435
    }
436
437
    /**
438
     * Return array item by key.
439
     *
440
     * @param array  $array
441
     * @param string $key
442
     * @param mixed  $default
443
     *
444
     * @return mixed
445
     */
446
    public function arrayItem(array $array, $key, $default = null)
447
    {
448
        if (is_null($key)) {
449
            return $array;
450
        }
451
452
        if (isset($array[$key])) {
453
            return $array[$key];
454
        }
455
456
        foreach (explode('.', $key) as $segment) {
457
            if (!is_array($array) || !array_key_exists($segment, $array)) {
458
                return $default;
459
            }
460
461
            $array = $array[$segment];
462
        }
463
464
        return $array;
465
    }
466
}
467