SignedRequest::getRawSignedRequest()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
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;
25
26
use Facebook\Exceptions\FacebookSDKException;
27
28
/**
29
 * Class SignedRequest
30
 *
31
 * @package Facebook
32
 */
33
class SignedRequest
34
{
35
    /**
36
     * @var FacebookApp The FacebookApp entity.
37
     */
38
    protected $app;
39
40
    /**
41
     * @var string The raw encrypted signed request.
42
     */
43
    protected $rawSignedRequest;
44
45
    /**
46
     * @var array The payload from the decrypted signed request.
47
     */
48
    protected $payload;
49
50
    /**
51
     * Instantiate a new SignedRequest entity.
52
     *
53
     * @param FacebookApp $facebookApp      The FacebookApp entity.
54
     * @param string|null $rawSignedRequest The raw signed request.
55
     */
56
    public function __construct(FacebookApp $facebookApp, $rawSignedRequest = null)
57
    {
58
        $this->app = $facebookApp;
59
60
        if (!$rawSignedRequest) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $rawSignedRequest 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...
61
            return;
62
        }
63
64
        $this->rawSignedRequest = $rawSignedRequest;
65
66
        $this->parse();
67
    }
68
69
    /**
70
     * Returns the raw signed request data.
71
     *
72
     * @return string|null
73
     */
74
    public function getRawSignedRequest()
75
    {
76
        return $this->rawSignedRequest;
77
    }
78
79
    /**
80
     * Returns the parsed signed request data.
81
     *
82
     * @return array|null
83
     */
84
    public function getPayload()
85
    {
86
        return $this->payload;
87
    }
88
89
    /**
90
     * Returns a property from the signed request data if available.
91
     *
92
     * @param string     $key
93
     * @param mixed|null $default
94
     *
95
     * @return mixed|null
96
     */
97
    public function get($key, $default = null)
98
    {
99
        if (isset($this->payload[$key])) {
100
            return $this->payload[$key];
101
        }
102
103
        return $default;
104
    }
105
106
    /**
107
     * Returns user_id from signed request data if available.
108
     *
109
     * @return string|null
110
     */
111
    public function getUserId()
112
    {
113
        return $this->get('user_id');
114
    }
115
116
    /**
117
     * Checks for OAuth data in the payload.
118
     *
119
     * @return boolean
120
     */
121
    public function hasOAuthData()
122
    {
123
        return $this->get('oauth_token') || $this->get('code');
124
    }
125
126
    /**
127
     * Creates a signed request from an array of data.
128
     *
129
     * @param array $payload
130
     *
131
     * @return string
132
     */
133
    public function make(array $payload)
134
    {
135
        $payload['algorithm'] = isset($payload['algorithm']) ? $payload['algorithm'] : 'HMAC-SHA256';
136
        $payload['issued_at'] = isset($payload['issued_at']) ? $payload['issued_at'] : time();
137
        $encodedPayload = $this->base64UrlEncode(json_encode($payload));
138
139
        $hashedSig = $this->hashSignature($encodedPayload);
140
        $encodedSig = $this->base64UrlEncode($hashedSig);
141
142
        return $encodedSig . '.' . $encodedPayload;
143
    }
144
145
    /**
146
     * Validates and decodes a signed request and saves
147
     * the payload to an array.
148
     */
149
    protected function parse()
150
    {
151
        list($encodedSig, $encodedPayload) = $this->split();
152
153
        // Signature validation
154
        $sig = $this->decodeSignature($encodedSig);
155
        $hashedSig = $this->hashSignature($encodedPayload);
156
        $this->validateSignature($hashedSig, $sig);
157
158
        $this->payload = $this->decodePayload($encodedPayload);
159
160
        // Payload validation
161
        $this->validateAlgorithm();
162
    }
163
164
    /**
165
     * Splits a raw signed request into signature and payload.
166
     *
167
     * @returns array
168
     *
169
     * @throws FacebookSDKException
170
     */
171
    protected function split()
172
    {
173
        if (strpos($this->rawSignedRequest, '.') === false) {
174
            throw new FacebookSDKException('Malformed signed request.', 606);
175
        }
176
177
        return explode('.', $this->rawSignedRequest, 2);
178
    }
179
180
    /**
181
     * Decodes the raw signature from a signed request.
182
     *
183
     * @param string $encodedSig
184
     *
185
     * @returns string
186
     *
187
     * @throws FacebookSDKException
188
     */
189
    protected function decodeSignature($encodedSig)
190
    {
191
        $sig = $this->base64UrlDecode($encodedSig);
192
193
        if (!$sig) {
194
            throw new FacebookSDKException('Signed request has malformed encoded signature data.', 607);
195
        }
196
197
        return $sig;
198
    }
199
200
    /**
201
     * Decodes the raw payload from a signed request.
202
     *
203
     * @param string $encodedPayload
204
     *
205
     * @returns array
206
     *
207
     * @throws FacebookSDKException
208
     */
209
    protected function decodePayload($encodedPayload)
210
    {
211
        $payload = $this->base64UrlDecode($encodedPayload);
212
213
        if ($payload) {
214
            $payload = json_decode($payload, true);
215
        }
216
217
        if (!is_array($payload)) {
218
            throw new FacebookSDKException('Signed request has malformed encoded payload data.', 607);
219
        }
220
221
        return $payload;
222
    }
223
224
    /**
225
     * Validates the algorithm used in a signed request.
226
     *
227
     * @throws FacebookSDKException
228
     */
229
    protected function validateAlgorithm()
230
    {
231
        if ($this->get('algorithm') !== 'HMAC-SHA256') {
232
            throw new FacebookSDKException('Signed request is using the wrong algorithm.', 605);
233
        }
234
    }
235
236
    /**
237
     * Hashes the signature used in a signed request.
238
     *
239
     * @param string $encodedData
240
     *
241
     * @return string
242
     *
243
     * @throws FacebookSDKException
244
     */
245
    protected function hashSignature($encodedData)
246
    {
247
        $hashedSig = hash_hmac(
248
            'sha256',
249
            $encodedData,
250
            $this->app->getSecret(),
251
            $raw_output = true
252
        );
253
254
        if (!$hashedSig) {
255
            throw new FacebookSDKException('Unable to hash signature from encoded payload data.', 602);
256
        }
257
258
        return $hashedSig;
259
    }
260
261
    /**
262
     * Validates the signature used in a signed request.
263
     *
264
     * @param string $hashedSig
265
     * @param string $sig
266
     *
267
     * @throws FacebookSDKException
268
     */
269
    protected function validateSignature($hashedSig, $sig)
270
    {
271
        if (\hash_equals($hashedSig, $sig)) {
272
            return;
273
        }
274
275
        throw new FacebookSDKException('Signed request has an invalid signature.', 602);
276
    }
277
278
    /**
279
     * Base64 decoding which replaces characters:
280
     *   + instead of -
281
     *   / instead of _
282
     *
283
     * @link http://en.wikipedia.org/wiki/Base64#URL_applications
284
     *
285
     * @param string $input base64 url encoded input
286
     *
287
     * @return string decoded string
288
     */
289
    public function base64UrlDecode($input)
290
    {
291
        $urlDecodedBase64 = strtr($input, '-_', '+/');
292
        $this->validateBase64($urlDecodedBase64);
293
294
        return base64_decode($urlDecodedBase64);
295
    }
296
297
    /**
298
     * Base64 encoding which replaces characters:
299
     *   + instead of -
300
     *   / instead of _
301
     *
302
     * @link http://en.wikipedia.org/wiki/Base64#URL_applications
303
     *
304
     * @param string $input string to encode
305
     *
306
     * @return string base64 url encoded input
307
     */
308
    public function base64UrlEncode($input)
309
    {
310
        return strtr(base64_encode($input), '+/', '-_');
311
    }
312
313
    /**
314
     * Validates a base64 string.
315
     *
316
     * @param string $input base64 value to validate
317
     *
318
     * @throws FacebookSDKException
319
     */
320
    protected function validateBase64($input)
321
    {
322
        if (!preg_match('/^[a-zA-Z0-9\/\r\n+]*={0,2}$/', $input)) {
323
            throw new FacebookSDKException('Signed request contains malformed base64 encoding.', 608);
324
        }
325
    }
326
}
327