Completed
Push — master ( b44e2f...535eb8 )
by
unknown
03:51 queued 46s
created

FacebookRedirectLoginHelper::resetCsrf()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 4
rs 10
1
<?php
2
/**
3
 * Copyright 2016 Facebook, Inc.
4
 *
5
 * You are hereby granted a non-exclusive, worldwide, royalty-free license to
6
 * use, copy, modify, and distribute this software in source code or binary
7
 * form for use in connection with the web services and APIs provided by
8
 * Facebook.
9
 *
10
 * As with any software that integrates with the Facebook platform, your use
11
 * of this software is subject to the Facebook Developer Principles and
12
 * Policies [http://developers.facebook.com/policy/]. This copyright notice
13
 * shall be included in all copies or substantial portions of the software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21
 * DEALINGS IN THE SOFTWARE.
22
 *
23
 */
24
namespace Facebook\Helpers;
25
26
use Facebook\Authentication\AccessToken;
27
use Facebook\Authentication\OAuth2Client;
28
use Facebook\Exceptions\FacebookSDKException;
29
use Facebook\PersistentData\FacebookSessionPersistentDataHandler;
30
use Facebook\PersistentData\PersistentDataInterface;
31
use Facebook\PseudoRandomString\PseudoRandomStringGeneratorFactory;
32
use Facebook\PseudoRandomString\PseudoRandomStringGeneratorInterface;
33
use Facebook\Url\FacebookUrlDetectionHandler;
34
use Facebook\Url\FacebookUrlManipulator;
35
use Facebook\Url\UrlDetectionInterface;
36
37
/**
38
 * Class FacebookRedirectLoginHelper
39
 *
40
 * @package Facebook
41
 */
42
class FacebookRedirectLoginHelper
43
{
44
    /**
45
     * @const int The length of CSRF string to validate the login link.
46
     */
47
    const CSRF_LENGTH = 32;
48
49
    /**
50
     * @var OAuth2Client The OAuth 2.0 client service.
51
     */
52
    protected $oAuth2Client;
53
54
    /**
55
     * @var UrlDetectionInterface The URL detection handler.
56
     */
57
    protected $urlDetectionHandler;
58
59
    /**
60
     * @var PersistentDataInterface The persistent data handler.
61
     */
62
    protected $persistentDataHandler;
63
64
    /**
65
     * @var PseudoRandomStringGeneratorInterface The cryptographically secure pseudo-random string generator.
66
     */
67
    protected $pseudoRandomStringGenerator;
68
69
    /**
70
     * @param OAuth2Client                              $oAuth2Client          The OAuth 2.0 client service.
71
     * @param PersistentDataInterface|null              $persistentDataHandler The persistent data handler.
72
     * @param UrlDetectionInterface|null                $urlHandler            The URL detection handler.
73
     * @param PseudoRandomStringGeneratorInterface|null $prsg                  The cryptographically secure pseudo-random string generator.
74
     */
75
    public function __construct(OAuth2Client $oAuth2Client, PersistentDataInterface $persistentDataHandler = null, UrlDetectionInterface $urlHandler = null, PseudoRandomStringGeneratorInterface $prsg = null)
76
    {
77
        $this->oAuth2Client = $oAuth2Client;
78
        $this->persistentDataHandler = $persistentDataHandler ?: new FacebookSessionPersistentDataHandler();
79
        $this->urlDetectionHandler = $urlHandler ?: new FacebookUrlDetectionHandler();
80
        $this->pseudoRandomStringGenerator = PseudoRandomStringGeneratorFactory::createPseudoRandomStringGenerator($prsg);
81
    }
82
83
    /**
84
     * Returns the persistent data handler.
85
     *
86
     * @return PersistentDataInterface
87
     */
88
    public function getPersistentDataHandler()
89
    {
90
        return $this->persistentDataHandler;
91
    }
92
93
    /**
94
     * Returns the URL detection handler.
95
     *
96
     * @return UrlDetectionInterface
97
     */
98
    public function getUrlDetectionHandler()
99
    {
100
        return $this->urlDetectionHandler;
101
    }
102
103
    /**
104
     * Returns the cryptographically secure pseudo-random string generator.
105
     *
106
     * @return PseudoRandomStringGeneratorInterface
107
     */
108
    public function getPseudoRandomStringGenerator()
109
    {
110
        return $this->pseudoRandomStringGenerator;
111
    }
112
113
    /**
114
     * Stores CSRF state and returns a URL to which the user should be sent to in order to continue the login process with Facebook.
115
     *
116
     * @param string $redirectUrl The URL Facebook should redirect users to after login.
117
     * @param array  $scope       List of permissions to request during login.
118
     * @param array  $params      An array of parameters to generate URL.
119
     * @param string $separator   The separator to use in http_build_query().
120
     *
121
     * @return string
122
     */
123
    private function makeUrl($redirectUrl, array $scope, array $params = [], $separator = '&')
124
    {
125
        $state = $this->persistentDataHandler->get('state') ?: $this->pseudoRandomStringGenerator->getPseudoRandomString(static::CSRF_LENGTH);
126
        $this->persistentDataHandler->set('state', $state);
127
128
        return $this->oAuth2Client->getAuthorizationUrl($redirectUrl, $state, $scope, $params, $separator);
129
    }
130
131
    /**
132
     * Returns the URL to send the user in order to login to Facebook.
133
     *
134
     * @param string $redirectUrl The URL Facebook should redirect users to after login.
135
     * @param array  $scope       List of permissions to request during login.
136
     * @param string $separator   The separator to use in http_build_query().
137
     *
138
     * @return string
139
     */
140
    public function getLoginUrl($redirectUrl, array $scope = [], $separator = '&')
141
    {
142
        return $this->makeUrl($redirectUrl, $scope, [], $separator);
143
    }
144
145
    /**
146
     * Returns the URL to send the user in order to log out of Facebook.
147
     *
148
     * @param AccessToken|string $accessToken The access token that will be logged out.
149
     * @param string             $next        The url Facebook should redirect the user to after a successful logout.
150
     * @param string             $separator   The separator to use in http_build_query().
151
     *
152
     * @return string
153
     *
154
     * @throws FacebookSDKException
155
     */
156
    public function getLogoutUrl($accessToken, $next, $separator = '&')
157
    {
158
        if (!$accessToken instanceof AccessToken) {
159
            $accessToken = new AccessToken($accessToken);
160
        }
161
162
        if ($accessToken->isAppAccessToken()) {
163
            throw new FacebookSDKException('Cannot generate a logout URL with an app access token.', 722);
164
        }
165
166
        $params = [
167
            'next' => $next,
168
            'access_token' => $accessToken->getValue(),
169
        ];
170
171
        return 'https://www.facebook.com/logout.php?' . http_build_query($params, null, $separator);
172
    }
173
174
    /**
175
     * Returns the URL to send the user in order to login to Facebook with permission(s) to be re-asked.
176
     *
177
     * @param string $redirectUrl The URL Facebook should redirect users to after login.
178
     * @param array  $scope       List of permissions to request during login.
179
     * @param string $separator   The separator to use in http_build_query().
180
     *
181
     * @return string
182
     */
183
    public function getReRequestUrl($redirectUrl, array $scope = [], $separator = '&')
184
    {
185
        $params = ['auth_type' => 'rerequest'];
186
187
        return $this->makeUrl($redirectUrl, $scope, $params, $separator);
188
    }
189
190
    /**
191
     * Returns the URL to send the user in order to login to Facebook with user to be re-authenticated.
192
     *
193
     * @param string $redirectUrl The URL Facebook should redirect users to after login.
194
     * @param array  $scope       List of permissions to request during login.
195
     * @param string $separator   The separator to use in http_build_query().
196
     *
197
     * @return string
198
     */
199
    public function getReAuthenticationUrl($redirectUrl, array $scope = [], $separator = '&')
200
    {
201
        $params = ['auth_type' => 'reauthenticate'];
202
203
        return $this->makeUrl($redirectUrl, $scope, $params, $separator);
204
    }
205
206
    /**
207
     * Takes a valid code from a login redirect, and returns an AccessToken entity.
208
     *
209
     * @param string|null $redirectUrl The redirect URL.
210
     *
211
     * @return AccessToken|null
212
     *
213
     * @throws FacebookSDKException
214
     */
215
    public function getAccessToken($redirectUrl = null)
216
    {
217
        if (!$code = $this->getCode()) {
218
            return null;
219
        }
220
221
        $this->validateCsrf();
222
        $this->resetCsrf();
223
224
        $redirectUrl = $redirectUrl ?: $this->urlDetectionHandler->getCurrentUrl();
225
        // At minimum we need to remove the state param
226
        $redirectUrl = FacebookUrlManipulator::removeParamsFromUrl($redirectUrl, ['state']);
227
228
        return $this->oAuth2Client->getAccessTokenFromCode($code, $redirectUrl);
229
    }
230
231
    /**
232
     * Validate the request against a cross-site request forgery.
233
     *
234
     * @throws FacebookSDKException
235
     */
236
    protected function validateCsrf()
237
    {
238
        $state = $this->getState();
239
        if (!$state) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $state of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
240
            throw new FacebookSDKException('Cross-site request forgery validation failed. Required GET param "state" missing.');
241
        }
242
        $savedState = $this->persistentDataHandler->get('state');
243
        if (!$savedState) {
244
            throw new FacebookSDKException('Cross-site request forgery validation failed. Required param "state" missing from persistent data.');
245
        }
246
247
        if (\hash_equals($savedState, $state)) {
248
            return;
249
        }
250
251
        throw new FacebookSDKException('Cross-site request forgery validation failed. The "state" param from the URL and session do not match.');
252
    }
253
254
    /**
255
     * Resets the CSRF so that it doesn't get reused.
256
     */
257
    private function resetCsrf()
258
    {
259
        $this->persistentDataHandler->set('state', null);
260
    }
261
262
    /**
263
     * Return the code.
264
     *
265
     * @return string|null
266
     */
267
    protected function getCode()
268
    {
269
        return $this->getInput('code');
270
    }
271
272
    /**
273
     * Return the state.
274
     *
275
     * @return string|null
276
     */
277
    protected function getState()
278
    {
279
        return $this->getInput('state');
280
    }
281
282
    /**
283
     * Return the error code.
284
     *
285
     * @return string|null
286
     */
287
    public function getErrorCode()
288
    {
289
        return $this->getInput('error_code');
290
    }
291
292
    /**
293
     * Returns the error.
294
     *
295
     * @return string|null
296
     */
297
    public function getError()
298
    {
299
        return $this->getInput('error');
300
    }
301
302
    /**
303
     * Returns the error reason.
304
     *
305
     * @return string|null
306
     */
307
    public function getErrorReason()
308
    {
309
        return $this->getInput('error_reason');
310
    }
311
312
    /**
313
     * Returns the error description.
314
     *
315
     * @return string|null
316
     */
317
    public function getErrorDescription()
318
    {
319
        return $this->getInput('error_description');
320
    }
321
322
    /**
323
     * Returns a value from a GET param.
324
     *
325
     * @param string $key
326
     *
327
     * @return string|null
328
     */
329
    private function getInput($key)
0 ignored issues
show
Coding Style introduced by
getInput uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
330
    {
331
        return isset($_GET[$key]) ? $_GET[$key] : null;
332
    }
333
}
334