Subversion Repositories cheapmusic

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
25 - 1
<?php
2
/**
3
 * Copyright 2017 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) {
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
     * @return 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
     * @return 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
     * @return 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
}