From fdc66742dae79c787408fb4bb070fae13abd9122 Mon Sep 17 00:00:00 2001
From: Andjelko Horvat <comel@vingd.com>
Date: Wed, 4 Jan 2012 12:30:54 +0000
Subject: [PATCH] authfacebook: update base_facebook.php to 3.1.1.

git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@3006 44740490-163a-0410-bde0-09ae8108e29a
---
 .../authfacebook/extlibinc/base_facebook.php  | 86 ++++++++++++++++---
 .../authfacebook/lib/Auth/Source/Facebook.php |  4 +-
 modules/authfacebook/lib/Facebook.php         |  2 +-
 3 files changed, 77 insertions(+), 15 deletions(-)

diff --git a/modules/authfacebook/extlibinc/base_facebook.php b/modules/authfacebook/extlibinc/base_facebook.php
index 0805c153c..fcf3bff1e 100644
--- a/modules/authfacebook/extlibinc/base_facebook.php
+++ b/modules/authfacebook/extlibinc/base_facebook.php
@@ -110,7 +110,7 @@ class FacebookApiException extends Exception
  * Provides access to the Facebook Platform.  This class provides
  * a majority of the functionality needed, but the class is abstract
  * because it is designed to be sub-classed.  The subclass must
- * implement the three abstract methods listed at the bottom of
+ * implement the four abstract methods listed at the bottom of
  * the file.
  *
  * @author Naitik Shah <naitik@facebook.com>
@@ -120,7 +120,7 @@ abstract class BaseFacebook
   /**
    * Version.
    */
-  const VERSION = '3.0.1';
+  const VERSION = '3.1.1';
 
   /**
    * Default options for curl.
@@ -129,7 +129,7 @@ abstract class BaseFacebook
     CURLOPT_CONNECTTIMEOUT => 10,
     CURLOPT_RETURNTRANSFER => true,
     CURLOPT_TIMEOUT        => 60,
-    CURLOPT_USERAGENT      => 'facebook-php-3.0',
+    CURLOPT_USERAGENT      => 'facebook-php-3.1',
   );
 
   /**
@@ -314,7 +314,8 @@ abstract class BaseFacebook
     // access token, in case we navigate to the /oauth/access_token
     // endpoint, where SOME access token is required.
     $this->setAccessToken($this->getApplicationAccessToken());
-    if ($user_access_token = $this->getUserAccessToken()) {
+    $user_access_token = $this->getUserAccessToken();
+    if ($user_access_token) {
       $this->setAccessToken($user_access_token);
     }
 
@@ -337,12 +338,24 @@ abstract class BaseFacebook
     // the access token.
     $signed_request = $this->getSignedRequest();
     if ($signed_request) {
+      // apps.facebook.com hands the access_token in the signed_request
       if (array_key_exists('oauth_token', $signed_request)) {
         $access_token = $signed_request['oauth_token'];
         $this->setPersistentData('access_token', $access_token);
         return $access_token;
       }
 
+      // the JS SDK puts a code in with the redirect_uri of ''
+      if (array_key_exists('code', $signed_request)) {
+        $code = $signed_request['code'];
+        $access_token = $this->getAccessTokenFromCode($code, '');
+        if ($access_token) {
+          $this->setPersistentData('code', $code);
+          $this->setPersistentData('access_token', $access_token);
+          return $access_token;
+        }
+      }
+
       // signed request states there's no access token, so anything
       // stored should be cleared.
       $this->clearAllPersistentData();
@@ -372,15 +385,19 @@ abstract class BaseFacebook
   }
 
   /**
-   * Get the data from a signed_request token.
+   * Retrieve the signed request, either from a request parameter or,
+   * if not present, from a cookie.
    *
-   * @return string The base domain
+   * @return string the signed request, if available, or null otherwise.
    */
   public function getSignedRequest() {
     if (!$this->signedRequest) {
       if (isset($_REQUEST['signed_request'])) {
         $this->signedRequest = $this->parseSignedRequest(
           $_REQUEST['signed_request']);
+      } else if (isset($_COOKIE[$this->getSignedRequestCookieName()])) {
+        $this->signedRequest = $this->parseSignedRequest(
+          $_COOKIE[$this->getSignedRequestCookieName()]);
       }
     }
     return $this->signedRequest;
@@ -461,6 +478,13 @@ abstract class BaseFacebook
   public function getLoginUrl($params=array()) {
     $this->establishCSRFTokenState();
     $currentUrl = $this->getCurrentUrl();
+
+    // if 'scope' is passed as an array, convert to comma separated list
+    $scopeParams = isset($params['scope']) ? $params['scope'] : null;
+    if ($scopeParams && is_array($scopeParams)) {
+      $params['scope'] = implode(',', $scopeParams);
+    }
+
     return $this->getUrl(
       'www',
       'dialog/oauth',
@@ -530,6 +554,19 @@ abstract class BaseFacebook
     }
   }
 
+  /**
+   * Constructs and returns the name of the cookie that
+   * potentially houses the signed request for the app user.
+   * The cookie is not set by the BaseFacebook class, but
+   * it may be set by the JavaScript SDK.
+   *
+   * @return string the name of the cookie that would house
+   *         the signed request value.
+   */
+  protected function getSignedRequestCookieName() {
+    return 'fbsr_'.$this->getAppId();
+  }
+
   /**
    * Get the authorization code from the query parameters, if it exists,
    * and otherwise return false to signal no authorization code was
@@ -611,11 +648,15 @@ abstract class BaseFacebook
    * @return mixed An access token exchanged for the authorization code, or
    *               false if an access token could not be generated.
    */
-  protected function getAccessTokenFromCode($code) {
+  protected function getAccessTokenFromCode($code, $redirect_uri = null) {
     if (empty($code)) {
       return false;
     }
 
+    if ($redirect_uri === null) {
+      $redirect_uri = $this->getCurrentUrl();
+    }
+
     try {
       // need to circumvent json_decode by calling _oauthRequest
       // directly, since response isn't JSON format.
@@ -624,7 +665,7 @@ abstract class BaseFacebook
           $this->getUrl('graph', '/oauth/access_token'),
           $params = array('client_id' => $this->getAppId(),
                           'client_secret' => $this->getApiSecret(),
-                          'redirect_uri' => $this->getCurrentUrl(),
+                          'redirect_uri' => $redirect_uri,
                           'code' => $code));
     } catch (FacebookApiException $e) {
       // most likely that user very recently revoked authorization.
@@ -665,7 +706,12 @@ abstract class BaseFacebook
 
     // results are returned, errors are thrown
     if (is_array($result) && isset($result['error_code'])) {
-      throw new FacebookApiException($result);
+      $this->throwAPIException($result);
+    }
+
+    if ($params['method'] === 'auth.expireSession' ||
+        $params['method'] === 'auth.revokeAuthorization') {
+      $this->destroySession();
     }
 
     return $result;
@@ -922,9 +968,14 @@ abstract class BaseFacebook
    * @return string The current URL
    */
   protected function getCurrentUrl() {
-    $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on'
-      ? 'https://'
-      : 'http://';
+    if (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] == 1)
+      || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https'
+    ) {
+      $protocol = 'https://';
+    }
+    else {
+      $protocol = 'http://';
+    }
     $currentUrl = $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
     $parts = parse_url($currentUrl);
 
@@ -991,6 +1042,8 @@ abstract class BaseFacebook
       case 'OAuthException':
         // OAuth 2.0 Draft 10 style
       case 'invalid_token':
+        // REST server errors are just Exceptions
+      case 'Exception':
         $message = $e->getMessage();
       if ((strpos($message, 'Error validating access token') !== false) ||
           (strpos($message, 'Invalid OAuth access token') !== false)) {
@@ -1033,6 +1086,15 @@ abstract class BaseFacebook
     return base64_decode(strtr($input, '-_', '+/'));
   }
 
+  /**
+   * Destroy the current session
+   */
+  public function destroySession() {
+    $this->setAccessToken(null);
+    $this->user = 0;
+    $this->clearAllPersistentData();
+  }
+
   /**
    * Each of the following four methods should be overridden in
    * a concrete subclass, as they are in the provided Facebook class.
diff --git a/modules/authfacebook/lib/Auth/Source/Facebook.php b/modules/authfacebook/lib/Auth/Source/Facebook.php
index 3c9655ef2..642db0bea 100644
--- a/modules/authfacebook/lib/Auth/Source/Facebook.php
+++ b/modules/authfacebook/lib/Auth/Source/Facebook.php
@@ -74,7 +74,7 @@ class sspmod_authfacebook_Auth_Source_Facebook extends SimpleSAML_Auth_Source {
 		$stateID = SimpleSAML_Auth_State::saveState($state, self::STAGE_INIT);
 		
 		$facebook = new sspmod_authfacebook_Facebook(array('appId' => $this->api_key, 'secret' => $this->secret), $state);
-		$facebook->clearAllPersistentData();
+		$facebook->destroySession();
 
 		$linkback = SimpleSAML_Module::getModuleURL('authfacebook/linkback.php', array('AuthState' => $stateID));
 		$url = $facebook->getLoginUrl(array('redirect_uri' => $linkback, 'scope' => $this->req_perms));
@@ -122,7 +122,7 @@ class sspmod_authfacebook_Auth_Source_Facebook extends SimpleSAML_Auth_Source {
 
 		$state['Attributes'] = $attributes;
 	
-		$facebook->clearAllPersistentData();
+		$facebook->destroySession();
 	}
 
 }
diff --git a/modules/authfacebook/lib/Facebook.php b/modules/authfacebook/lib/Facebook.php
index d31e90f29..7fc2ba502 100644
--- a/modules/authfacebook/lib/Facebook.php
+++ b/modules/authfacebook/lib/Facebook.php
@@ -71,7 +71,7 @@ class sspmod_authfacebook_Facebook extends BaseFacebook
     }
   }
 
-  public function clearAllPersistentData() {
+  protected function clearAllPersistentData() {
     foreach (self::$kSupportedKeys as $key) {
       $this->clearPersistentData($key);
     }
-- 
GitLab