Completed
Push — master ( 51b8e0...0f94ca )
by ARCANEDEV
06:17
created

OAuthTwoProvider::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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