123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386 |
- <?php
- /**
- * Copyright 2014 Facebook, Inc.
- *
- * You are hereby granted a non-exclusive, worldwide, royalty-free license to
- * use, copy, modify, and distribute this software in source code or binary
- * form for use in connection with the web services and APIs provided by
- * Facebook.
- *
- * As with any software that integrates with the Facebook platform, your use
- * of this software is subject to the Facebook Developer Principles and
- * Policies [http://developers.facebook.com/policy/]. This copyright notice
- * shall be included in all copies or substantial portions of the software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- *
- */
- namespace Facebook\Entities;
- use Facebook\FacebookSDKException;
- use Facebook\FacebookSession;
- /**
- * Class SignedRequest
- * @package Facebook
- */
- class SignedRequest
- {
- /**
- * @var string
- */
- public $rawSignedRequest;
- /**
- * @var array
- */
- public $payload;
- /**
- * Instantiate a new SignedRequest entity.
- *
- * @param string|null $rawSignedRequest The raw signed request.
- * @param string|null $state random string to prevent CSRF.
- * @param string|null $appSecret
- */
- public function __construct($rawSignedRequest = null, $state = null, $appSecret = null)
- {
- if (!$rawSignedRequest) {
- return;
- }
- $this->rawSignedRequest = $rawSignedRequest;
- $this->payload = static::parse($rawSignedRequest, $state, $appSecret);
- }
- /**
- * Returns the raw signed request data.
- *
- * @return string|null
- */
- public function getRawSignedRequest()
- {
- return $this->rawSignedRequest;
- }
- /**
- * Returns the parsed signed request data.
- *
- * @return array|null
- */
- public function getPayload()
- {
- return $this->payload;
- }
- /**
- * Returns a property from the signed request data if available.
- *
- * @param string $key
- * @param mixed|null $default
- *
- * @return mixed|null
- */
- public function get($key, $default = null)
- {
- if (isset($this->payload[$key])) {
- return $this->payload[$key];
- }
- return $default;
- }
- /**
- * Returns user_id from signed request data if available.
- *
- * @return string|null
- */
- public function getUserId()
- {
- return $this->get('user_id');
- }
- /**
- * Checks for OAuth data in the payload.
- *
- * @return boolean
- */
- public function hasOAuthData()
- {
- return isset($this->payload['oauth_token']) || isset($this->payload['code']);
- }
- /**
- * Creates a signed request from an array of data.
- *
- * @param array $payload
- * @param string|null $appSecret
- *
- * @return string
- */
- public static function make(array $payload, $appSecret = null)
- {
- $payload['algorithm'] = 'HMAC-SHA256';
- $payload['issued_at'] = time();
- $encodedPayload = static::base64UrlEncode(json_encode($payload));
- $hashedSig = static::hashSignature($encodedPayload, $appSecret);
- $encodedSig = static::base64UrlEncode($hashedSig);
- return $encodedSig.'.'.$encodedPayload;
- }
- /**
- * Validates and decodes a signed request and returns
- * the payload as an array.
- *
- * @param string $signedRequest
- * @param string|null $state
- * @param string|null $appSecret
- *
- * @return array
- */
- public static function parse($signedRequest, $state = null, $appSecret = null)
- {
- list($encodedSig, $encodedPayload) = static::split($signedRequest);
- // Signature validation
- $sig = static::decodeSignature($encodedSig);
- $hashedSig = static::hashSignature($encodedPayload, $appSecret);
- static::validateSignature($hashedSig, $sig);
- // Payload validation
- $data = static::decodePayload($encodedPayload);
- static::validateAlgorithm($data);
- if ($state) {
- static::validateCsrf($data, $state);
- }
- return $data;
- }
- /**
- * Validates the format of a signed request.
- *
- * @param string $signedRequest
- *
- * @throws FacebookSDKException
- */
- public static function validateFormat($signedRequest)
- {
- if (strpos($signedRequest, '.') !== false) {
- return;
- }
- throw new FacebookSDKException(
- 'Malformed signed request.', 606
- );
- }
- /**
- * Decodes a raw valid signed request.
- *
- * @param string $signedRequest
- *
- * @returns array
- */
- public static function split($signedRequest)
- {
- static::validateFormat($signedRequest);
- return explode('.', $signedRequest, 2);
- }
- /**
- * Decodes the raw signature from a signed request.
- *
- * @param string $encodedSig
- *
- * @returns string
- *
- * @throws FacebookSDKException
- */
- public static function decodeSignature($encodedSig)
- {
- $sig = static::base64UrlDecode($encodedSig);
- if ($sig) {
- return $sig;
- }
- throw new FacebookSDKException(
- 'Signed request has malformed encoded signature data.', 607
- );
- }
- /**
- * Decodes the raw payload from a signed request.
- *
- * @param string $encodedPayload
- *
- * @returns array
- *
- * @throws FacebookSDKException
- */
- public static function decodePayload($encodedPayload)
- {
- $payload = static::base64UrlDecode($encodedPayload);
- if ($payload) {
- $payload = json_decode($payload, true);
- }
- if (is_array($payload)) {
- return $payload;
- }
- throw new FacebookSDKException(
- 'Signed request has malformed encoded payload data.', 607
- );
- }
- /**
- * Validates the algorithm used in a signed request.
- *
- * @param array $data
- *
- * @throws FacebookSDKException
- */
- public static function validateAlgorithm(array $data)
- {
- if (isset($data['algorithm']) && $data['algorithm'] === 'HMAC-SHA256') {
- return;
- }
- throw new FacebookSDKException(
- 'Signed request is using the wrong algorithm.', 605
- );
- }
- /**
- * Hashes the signature used in a signed request.
- *
- * @param string $encodedData
- * @param string|null $appSecret
- *
- * @return string
- *
- * @throws FacebookSDKException
- */
- public static function hashSignature($encodedData, $appSecret = null)
- {
- $hashedSig = hash_hmac(
- 'sha256', $encodedData, FacebookSession::_getTargetAppSecret($appSecret), $raw_output = true
- );
- if ($hashedSig) {
- return $hashedSig;
- }
- throw new FacebookSDKException(
- 'Unable to hash signature from encoded payload data.', 602
- );
- }
- /**
- * Validates the signature used in a signed request.
- *
- * @param string $hashedSig
- * @param string $sig
- *
- * @throws FacebookSDKException
- */
- public static function validateSignature($hashedSig, $sig)
- {
- if (mb_strlen($hashedSig) === mb_strlen($sig)) {
- $validate = 0;
- for ($i = 0; $i < mb_strlen($sig); $i++) {
- $validate |= ord($hashedSig[$i]) ^ ord($sig[$i]);
- }
- if ($validate === 0) {
- return;
- }
- }
- throw new FacebookSDKException(
- 'Signed request has an invalid signature.', 602
- );
- }
- /**
- * Validates a signed request against CSRF.
- *
- * @param array $data
- * @param string $state
- *
- * @throws FacebookSDKException
- */
- public static function validateCsrf(array $data, $state)
- {
- if (isset($data['state']) && $data['state'] === $state) {
- return;
- }
- throw new FacebookSDKException(
- 'Signed request did not pass CSRF validation.', 604
- );
- }
- /**
- * Base64 decoding which replaces characters:
- * + instead of -
- * / instead of _
- * @link http://en.wikipedia.org/wiki/Base64#URL_applications
- *
- * @param string $input base64 url encoded input
- *
- * @return string decoded string
- */
- public static function base64UrlDecode($input)
- {
- $urlDecodedBase64 = strtr($input, '-_', '+/');
- static::validateBase64($urlDecodedBase64);
- return base64_decode($urlDecodedBase64);
- }
- /**
- * Base64 encoding which replaces characters:
- * + instead of -
- * / instead of _
- * @link http://en.wikipedia.org/wiki/Base64#URL_applications
- *
- * @param string $input string to encode
- *
- * @return string base64 url encoded input
- */
- public static function base64UrlEncode($input)
- {
- return strtr(base64_encode($input), '+/', '-_');
- }
- /**
- * Validates a base64 string.
- *
- * @param string $input base64 value to validate
- *
- * @throws FacebookSDKException
- */
- public static function validateBase64($input)
- {
- $pattern = '/^[a-zA-Z0-9\/\r\n+]*={0,2}$/';
- if (preg_match($pattern, $input)) {
- return;
- }
- throw new FacebookSDKException(
- 'Signed request contains malformed base64 encoding.', 608
- );
- }
- }
|