@@ -97,6 +97,7 @@ class Facebook
9797 */
9898 protected static $ DROP_QUERY_PARAMS = array (
9999 'session ' ,
100+ 'signed_request ' ,
100101 );
101102
102103 /**
@@ -124,6 +125,11 @@ class Facebook
124125 */
125126 protected $ session ;
126127
128+ /**
129+ * The data from the signed_request token.
130+ */
131+ protected $ signedRequest ;
132+
127133 /**
128134 * Indicates that we already loaded the session as best as we could.
129135 */
@@ -237,6 +243,21 @@ public function getBaseDomain() {
237243 return $ this ->baseDomain ;
238244 }
239245
246+ /**
247+ * Get the data from a signed_request token
248+ *
249+ * @return String the base domain
250+ */
251+ public function getSignedRequest () {
252+ if (!$ this ->signedRequest ) {
253+ if (isset ($ _REQUEST ['signed_request ' ])) {
254+ $ this ->signedRequest = $ this ->parseSignedRequest (
255+ $ _REQUEST ['signed_request ' ]);
256+ }
257+ }
258+ return $ this ->signedRequest ;
259+ }
260+
240261 /**
241262 * Set the Session.
242263 *
@@ -256,7 +277,7 @@ public function setSession($session=null, $write_cookie=true) {
256277
257278 /**
258279 * Get the session object. This will automatically look for a signed session
259- * sent via the Cookie or Query Parameters if needed.
280+ * sent via the signed_request, Cookie or Query Parameters if needed.
260281 *
261282 * @return Array the session
262283 */
@@ -265,8 +286,15 @@ public function getSession() {
265286 $ session = null ;
266287 $ write_cookie = true ;
267288
289+ // try loading session from signed_request in $_REQUEST
290+ $ signedRequest = $ this ->getSignedRequest ();
291+ if ($ signedRequest ) {
292+ // sig is good, use the signedRequest
293+ $ session = $ this ->createSessionFromSignedRequest ($ signedRequest );
294+ }
295+
268296 // try loading session from $_REQUEST
269- if (isset ($ _REQUEST ['session ' ])) {
297+ if (! $ session && isset ($ _REQUEST ['session ' ])) {
270298 $ session = json_decode (
271299 get_magic_quotes_gpc ()
272300 ? stripslashes ($ _REQUEST ['session ' ])
@@ -309,6 +337,21 @@ public function getUser() {
309337 return $ session ? $ session ['uid ' ] : null ;
310338 }
311339
340+ /**
341+ * Gets a OAuth access token.
342+ *
343+ * @return String the access token
344+ */
345+ public function getAccessToken () {
346+ $ session = $ this ->getSession ();
347+ // either user session signed, or app signed
348+ if ($ session ) {
349+ return $ session ['access_token ' ];
350+ } else {
351+ return $ this ->getAppId () .'| ' . $ this ->getApiSecret ();
352+ }
353+ }
354+
312355 /**
313356 * Get a Login URL for use with redirects. By default, full page redirect is
314357 * assumed. If you are using the generated URL with a window.open() call in
@@ -351,14 +394,12 @@ public function getLoginUrl($params=array()) {
351394 * @return String the URL for the logout flow
352395 */
353396 public function getLogoutUrl ($ params =array ()) {
354- $ session = $ this ->getSession ();
355397 return $ this ->getUrl (
356398 'www ' ,
357399 'logout.php ' ,
358400 array_merge (array (
359- 'api_key ' => $ this ->getAppId (),
360- 'next ' => $ this ->getCurrentUrl (),
361- 'session_key ' => $ session ['session_key ' ],
401+ 'next ' => $ this ->getCurrentUrl (),
402+ 'access_token ' => $ this ->getAccessToken (),
362403 ), $ params )
363404 );
364405 }
@@ -469,13 +510,7 @@ protected function _graph($path, $method='GET', $params=array()) {
469510 */
470511 protected function _oauthRequest ($ url , $ params ) {
471512 if (!isset ($ params ['access_token ' ])) {
472- $ session = $ this ->getSession ();
473- // either user session signed, or app signed
474- if ($ session ) {
475- $ params ['access_token ' ] = $ session ['access_token ' ];
476- } else {
477- $ params ['access_token ' ] = $ this ->getAppId () .'| ' . $ this ->getApiSecret ();
478- }
513+ $ params ['access_token ' ] = $ this ->getAccessToken ();
479514 }
480515
481516 // json_encode all params values that are not strings
@@ -576,7 +611,7 @@ protected function setCookieFromSession($session=null) {
576611 }
577612
578613 if (headers_sent ()) {
579- self ::error_log ('Could not set cookie. Headers already sent. ' );
614+ self ::errorLog ('Could not set cookie. Headers already sent. ' );
580615
581616 // ignore for code coverage as we will never be able to setcookie in a CLI
582617 // environment
@@ -597,8 +632,6 @@ protected function validateSessionObject($session) {
597632 // make sure some essential fields exist
598633 if (is_array ($ session ) &&
599634 isset ($ session ['uid ' ]) &&
600- isset ($ session ['session_key ' ]) &&
601- isset ($ session ['secret ' ]) &&
602635 isset ($ session ['access_token ' ]) &&
603636 isset ($ session ['sig ' ])) {
604637 // validate the signature
@@ -609,7 +642,7 @@ protected function validateSessionObject($session) {
609642 $ this ->getApiSecret ()
610643 );
611644 if ($ session ['sig ' ] != $ expected_sig ) {
612- self ::error_log ('Got invalid session signature in cookie. ' );
645+ self ::errorLog ('Got invalid session signature in cookie. ' );
613646 $ session = null ;
614647 }
615648 // check expiry time
@@ -619,6 +652,67 @@ protected function validateSessionObject($session) {
619652 return $ session ;
620653 }
621654
655+ /**
656+ * Returns something that looks like our JS session object from the
657+ * signed token's data
658+ *
659+ * TODO: Nuke this once the login flow uses OAuth2
660+ *
661+ * @param Array the output of getSignedRequest
662+ * @return Array Something that will work as a session
663+ */
664+ protected function createSessionFromSignedRequest ($ data ) {
665+ if (!isset ($ data ['oauth_token ' ])) {
666+ return null ;
667+ }
668+
669+ $ session = array (
670+ 'uid ' => $ data ['user_id ' ],
671+ 'access_token ' => $ data ['oauth_token ' ],
672+ 'expires ' => $ data ['expires ' ],
673+ );
674+
675+ // put a real sig, so that validateSignature works
676+ $ session ['sig ' ] = self ::generateSignature (
677+ $ session ,
678+ $ this ->getApiSecret ()
679+ );
680+
681+ return $ session ;
682+ }
683+
684+ /**
685+ * Parses a signed_request and validates the signature.
686+ * Then saves it in $this->signed_data
687+ *
688+ * @param String A signed token
689+ * @param Boolean Should we remove the parts of the payload that
690+ * are used by the algorithm?
691+ * @return Array the payload inside it or null if the sig is wrong
692+ */
693+ protected function parseSignedRequest ($ signed_request ) {
694+ list ($ encoded_sig , $ payload ) = explode ('. ' , $ signed_request , 2 );
695+
696+ // decode the data
697+ $ sig = self ::base64UrlDecode ($ encoded_sig );
698+ $ data = json_decode (self ::base64UrlDecode ($ payload ), true );
699+
700+ if (strtoupper ($ data ['algorithm ' ]) !== 'HMAC-SHA256 ' ) {
701+ self ::errorLog ('Unknown algorithm. Expected HMAC-SHA256 ' );
702+ return null ;
703+ }
704+
705+ // check sig
706+ $ expected_sig = hash_hmac ('sha256 ' , $ payload ,
707+ $ this ->getApiSecret (), $ raw = true );
708+ if ($ sig !== $ expected_sig ) {
709+ self ::errorLog ('Bad Signed JSON signature! ' );
710+ return null ;
711+ }
712+
713+ return $ data ;
714+ }
715+
622716 /**
623717 * Build the URL for api given parameters.
624718 *
@@ -775,11 +869,11 @@ protected static function generateSignature($params, $secret) {
775869 }
776870
777871 /**
778- * Prints to the error log if you aren't in command line mode.
872+ * Prints to the error log if you aren't in command line mode.
779873 *
780874 * @param String log message
781875 */
782- protected static function error_log ($ msg ) {
876+ protected static function errorLog ($ msg ) {
783877 // disable error log if we are running in a CLI environment
784878 // @codeCoverageIgnoreStart
785879 if (php_sapi_name () != 'cli ' ) {
@@ -789,4 +883,16 @@ protected static function error_log($msg) {
789883 // print 'error_log: '.$msg."\n";
790884 // @codeCoverageIgnoreEnd
791885 }
886+
887+ /**
888+ * Base64 encoding that doesn't need to be urlencode()ed.
889+ * Exactly the same as base64_encode except it uses
890+ * - instead of +
891+ * _ instead of /
892+ *
893+ * @param String base64UrlEncodeded string
894+ */
895+ protected static function base64UrlDecode ($ input ) {
896+ return base64_decode (strtr ($ input , '-_ ' , '+/ ' ));
897+ }
792898}
0 commit comments