diff --git a/README.md b/README.md index 599e7aa..527ff12 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,5 @@ CodeIgniter-Facebook A complete library giving you facebook oauth authentication and graph api access. +Loesely based on: [Elliot Haughin - CodeIgniter Facebook Library - Documentation](http://www.haughin.com/code/facebook/) \ No newline at end of file diff --git a/application/config/facebook.php b/application/config/facebook.php index 359cb2f..9906cd7 100644 --- a/application/config/facebook.php +++ b/application/config/facebook.php @@ -1,6 +1,10 @@ -load->add_package_path('/Users/elliot/github/codeigniter-facebook/application/'); - $this->load->library('facebook'); - $this->facebook->enable_debug(TRUE); + $this->load->library('facebook_api'); + $this->load->helper('facebook'); + $this->facebook_api->enable_debug(TRUE); } function index() @@ -33,27 +34,27 @@ function index() function login() { // This is the easiest way to keep your code up-to-date. Use git to checkout the - // codeigniter-facebook repo to a location outside of your site directory. + // codeigniter-facebook_api repo to a location outside of your site directory. // // Add the 'application' directory from the repo as a package path: // $this->load->add_package_path('/var/www/haughin.com/codeigniter-facebook/application/'); // // Then when you want to grab a fresh copy of the code, you can just run a git pull - // on your codeigniter-facebook directory. + // on your codeigniter-facebook_api directory. - if ( !$this->facebook->logged_in() ) + if ( !$this->facebook_api->logged_in() ) { // From now on, when we call login() or login_url(), the auth // will redirect back to this url. - $this->facebook->set_callback(site_url('facebook_test')); + $this->facebook_api->set_callback(site_url('facebook_test')); // Header redirection to auth. - $this->facebook->login(); + $this->facebook_api->login(); // You can alternatively create links in your HTML using - // $this->facebook->login_url(); as the href parameter. + // $this->facebook_api->login_url(); as the href parameter. } else { @@ -63,7 +64,7 @@ function login() function logout() { - $this->facebook->logout(); + $this->facebook_api->logout(); redirect('facebook_test'); } } \ No newline at end of file diff --git a/application/libraries/facebook.php b/application/libraries/facebook.php deleted file mode 100644 index d24c00b..0000000 --- a/application/libraries/facebook.php +++ /dev/null @@ -1,505 +0,0 @@ -_obj =& get_instance(); - - $this->_obj->load->library('session'); - $this->_obj->load->config('facebook'); - $this->_obj->load->helper('url'); - $this->_obj->load->helper('facebook'); - - $this->_api_url = $this->_obj->config->item('facebook_api_url'); - $this->_api_key = $this->_obj->config->item('facebook_app_id'); - $this->_api_secret = $this->_obj->config->item('facebook_api_secret'); - - $this->session = new facebookSession(); - $this->connection = new facebookConnection(); - } - - public function logged_in() - { - return $this->session->logged_in(); - } - - public function login($scope = NULL) - { - return $this->session->login($scope); - } - - public function login_url($scope = NULL) - { - return $this->session->login_url($scope); - } - - public function logout() - { - return $this->session->logout(); - } - - public function user() - { - return $this->session->get(); - } - - public function call($method, $uri, $data = array()) - { - $response = FALSE; - - try - { - switch ( $method ) - { - case 'get': - $response = $this->connection->get($this->append_token($this->_api_url.$uri)); - break; - - case 'post': - $response = $this->connection->post($this->append_token($this->_api_url.$uri), $data); - break; - } - } - catch (facebookException $e) - { - $this->_errors[] = $e; - - if ( $this->_enable_debug ) - { - echo $e; - } - } - - return $response; - } - - public function errors() - { - return $this->_errors; - } - - public function last_error() - { - if ( count($this->_errors) == 0 ) return NULL; - - return $this->_errors[count($this->_errors) - 1]; - } - - public function append_token($url) - { - return $this->session->append_token($url); - } - - public function set_callback($url) - { - return $this->session->set_callback($url); - } - - public function enable_debug($debug = TRUE) - { - $this->_enable_debug = (bool) $debug; - - - } - } - - class facebookConnection { - - // Allow multi-threading. - - private $_mch = NULL; - private $_properties = array(); - - function __construct() - { - $this->_mch = curl_multi_init(); - - $this->_properties = array( - 'code' => CURLINFO_HTTP_CODE, - 'time' => CURLINFO_TOTAL_TIME, - 'length' => CURLINFO_CONTENT_LENGTH_DOWNLOAD, - 'type' => CURLINFO_CONTENT_TYPE - ); - } - - private function _initConnection($url) - { - $this->_ch = curl_init($url); - curl_setopt($this->_ch, CURLOPT_RETURNTRANSFER, TRUE); - } - - public function get($url, $params = array()) - { - if ( count($params) > 0 ) - { - $url .= '?'; - - foreach( $params as $k => $v ) - { - $url .= "{$k}={$v}&"; - } - - $url = substr($url, 0, -1); - } - - $this->_initConnection($url); - $response = $this->_addCurl($url, $params); - - return $response; - } - - public function post($url, $params = array()) - { - // Todo - $post = ''; - - foreach ( $params as $k => $v ) - { - $post .= "{$k}={$v}&"; - } - - $post = substr($post, 0, -1); - - $this->_initConnection($url, $params); - curl_setopt($this->_ch, CURLOPT_POST, 1); - curl_setopt($this->_ch, CURLOPT_POSTFIELDS, $post); - - $response = $this->_addCurl($url, $params); - - return $response; - } - - private function _addCurl($url, $params = array()) - { - $ch = $this->_ch; - - $key = (string) $ch; - $this->_requests[$key] = $ch; - - $response = curl_multi_add_handle($this->_mch, $ch); - - if ( $response === CURLM_OK || $response === CURLM_CALL_MULTI_PERFORM ) - { - do { - $mch = curl_multi_exec($this->_mch, $active); - } while ( $mch === CURLM_CALL_MULTI_PERFORM ); - - return $this->_getResponse($key); - } - else - { - return $response; - } - } - - private function _getResponse($key = NULL) - { - if ( $key == NULL ) return FALSE; - - if ( isset($this->_responses[$key]) ) - { - return $this->_responses[$key]; - } - - $running = NULL; - - do - { - $response = curl_multi_exec($this->_mch, $running_curl); - - if ( $running !== NULL && $running_curl != $running ) - { - $this->_setResponse($key); - - if ( isset($this->_responses[$key]) ) - { - $response = new facebookResponse( (object) $this->_responses[$key] ); - - if ( $response->__resp->code !== 200 ) - { - $error = $response->__resp->code.' | Request Failed'; - - if ( isset($response->__resp->data->error->type) ) - { - $error .= ' - '.$response->__resp->data->error->type.' - '.$response->__resp->data->error->message; - } - - throw new facebookException($error); - } - - return $response; - } - } - - $running = $running_curl; - - } while ( $running_curl > 0); - - } - - private function _setResponse($key) - { - while( $done = curl_multi_info_read($this->_mch) ) - { - $key = (string) $done['handle']; - $this->_responses[$key]['data'] = curl_multi_getcontent($done['handle']); - - foreach ( $this->_properties as $curl_key => $value ) - { - $this->_responses[$key][$curl_key] = curl_getinfo($done['handle'], $value); - - curl_multi_remove_handle($this->_mch, $done['handle']); - } - } - } - } - - class facebookResponse { - - private $__construct; - - public function __construct($resp) - { - $this->__resp = $resp; - - $data = json_decode($this->__resp->data); - - if ( $data !== NULL ) - { - $this->__resp->data = $data; - } - } - - public function __get($name) - { - if ($this->__resp->code < 200 || $this->__resp->code > 299) return FALSE; - - $result = array(); - - if ( is_string($this->__resp->data ) ) - { - parse_str($this->__resp->data, $result); - $this->__resp->data = (object) $result; - } - - if ( $name === '_result') - { - return $this->__resp->data; - } - - return $this->__resp->data->$name; - } - } - - class facebookException extends Exception { - - function __construct($string) - { - parent::__construct($string); - } - - public function __toString() { - return "exception '".__CLASS__ ."' with message '".$this->getMessage()."' in ".$this->getFile().":".$this->getLine()."\nStack trace:\n".$this->getTraceAsString(); - } - } - - class facebookSession { - - private $_api_key; - private $_api_secret; - private $_token_url = 'oauth/access_token'; - private $_user_url = 'me'; - private $_data = array(); - - function __construct() - { - $this->_obj =& get_instance(); - - $this->_api_key = $this->_obj->config->item('facebook_app_id'); - $this->_api_secret = $this->_obj->config->item('facebook_api_secret'); - - $this->_token_url = $this->_obj->config->item('facebook_api_url').$this->_token_url; - $this->_user_url = $this->_obj->config->item('facebook_api_url').$this->_user_url; - - $this->_set('scope', $this->_obj->config->item('facebook_default_scope')); - - $this->connection = new facebookConnection(); - - if ( !$this->logged_in() ) - { - // Initializes the callback to this page URL. - $this->set_callback(); - } - - } - - public function logged_in() - { - return ( $this->get() === NULL ) ? FALSE : TRUE; - } - - public function logout() - { - $this->_unset('token'); - $this->_unset('user'); - } - - public function login_url($scope = NULL) - { - $url = "http://www.facebook.com/dialog/oauth?client_id=".$this->_api_key.'&redirect_uri='.urlencode($this->_get('callback')); - - if ( empty($scope) ) - { - $scope = $this->_get('scope'); - } - else - { - $this->_set('scope', $scope); - } - - if ( !empty($scope) ) - { - $url .= '&scope='.$scope; - } - - return $url; - } - - public function login($scope = NULL) - { - $this->logout(); - - if ( !$this->_get('callback') ) $this->_set('callback', current_url()); - - $url = $this->login_url($scope); - - return redirect($url); - } - - public function get() - { - $token = $this->_find_token(); - if ( empty($token) ) return NULL; - - // $user = $this->_get('user'); - // if ( !empty($user) ) return $user; - - try - { - $user = $this->connection->get($this->_user_url.'?'.$this->_token_string()); - } - catch ( facebookException $e ) - { - $this->logout(); - return NULL; - } - - // $this->_set('user', $user); - return $user; - } - - private function _find_token() - { - $token = $this->_get('token'); - - if ( !empty($token) ) - { - if ( !empty($token->expires) && intval($token->expires) >= time() ) - { - // Problem, token is expired! - return $this->logout(); - } - - $this->_set('token', $token); - return $this->_token_string(); - } - - if ( !isset($_GET['code']) ) - { - return $this->logout(); - } - - if ( !$this->_get('callback') ) $this->_set('callback', current_url()); - $token_url = $this->_token_url.'?client_id='.$this->_api_key."&client_secret=".$this->_api_secret."&code=".$_GET['code'].'&redirect_uri='.urlencode($this->_get('callback')); - - try - { - $token = $this->connection->get($token_url); - } - catch ( facebookException $e ) - { - $this->logout(); - redirect($this->_strip_query()); - return NULL; - } - - $this->_unset('callback'); - - if ( $token->access_token ) - { - if ( !empty($token->expires) ) - { - $token->expires = strval(time() + intval($token->expires)); - } - - $this->_set('token', $token); - redirect($this->_strip_query()); - } - - return $this->_token_string(); - } - - private function _get($key) - { - $key = '_facebook_'.$key; - return $this->_obj->session->userdata($key); - } - - private function _set($key, $data) - { - $key = '_facebook_'.$key; - $this->_obj->session->set_userdata($key, $data); - } - - private function _unset($key) - { - $key = '_facebook_'.$key; - $this->_obj->session->unset_userdata($key); - } - - public function set_callback($url = NULL) - { - $this->_set('callback', $this->_strip_query($url)); - } - - private function _token_string() - { - return 'access_token='.$this->_get('token')->access_token; - } - - public function append_token($url) - { - if ( $this->_get('token') ) $url .= '?'.$this->_token_string(); - - return $url; - } - - private function _strip_query($url = NULL) - { - if ( $url === NULL ) - { - $url = ( empty($_SERVER['HTTPS']) ) ? 'http' : 'https'; - $url .= '://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']; - } - - $parts = explode('?', $url); - return $parts[0]; - } - } \ No newline at end of file diff --git a/application/libraries/facebook_api.php b/application/libraries/facebook_api.php new file mode 100644 index 0000000..1717c41 --- /dev/null +++ b/application/libraries/facebook_api.php @@ -0,0 +1,381 @@ + + * @copyright Copyright (c) 2011, Benedikt Bauer + * @license http://dev.sam-song.info/license do what the fuck you want to public license v2 + */ +class Facebook_api +{ + /** + * Internal storage of the access_token + * @var string + */ + private $_token; + + /** + * Internal storage of token expiry + * @var integer + */ + private $_expires = 0; + + /** + * cURL handle + * @var resource + */ + private $_ch = NULL; + + /** + * Callback URL + * @var string + */ + private $_callback; + + /** + * Application ID + * @var string + */ + private $_appId; + + /** + * Application Key + * @var string + */ + private $_appKey; + + /** + * Application Secret + * @var string + */ + private $_appSecret; + + /** + * API Base URL + * @var string + */ + private $_apiURL; + + /** + * Instance of the CodeIgniter Main Class + * @var CodeIgniter + */ + private $_CI = NULL; + + + /** + * Load common config options into variables + * Load session library and URL helper + */ + public function __construct() + { + $this->_CI =& get_instance(); + $this->_CI->load->library('session'); + $this->_CI->load->config('facebook'); + $this->_CI->load->helper('url'); + + $this->_appId = $this->_CI->config->item('facebook_app_id'); + $this->_appKey = $this->_CI->config->item('facebook_app_key'); + $this->_appSecret = $this->_CI->config->item('facebook_app_secret'); + $this->_apiURL = $this->_CI->config->item('facebook_api_url'); + + $this->_token = $this->_CI->session->userdata('fb_token'); + } + + /** + * Initialize cURL with some default options + */ + private function _initCurl() + { + $this->_ch = curl_init(); + $config = array + ( + CURLOPT_CONNECTTIMEOUT => 10, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => 60, + CURLOPT_USERAGENT => 'facebook-php-2.0', + CURLOPT_HTTPHEADER => 'Expect:', + ); + curl_setopt_array($this->_ch, $config); + } + + /** + * Get access token from supplied code + * @param string $code + * @return bool + */ + private function _authorize($code) + { + // set parameters for oauth2.0 request + $options = array + ( + 'client_id' => $this->_appId, + 'client_secret' => $this->_appSecret, + 'code' => $code, + 'redirect_uri' => $this->_callback, + ); + + try + { + $token_reply = $this->call('get', 'oauth/access_token', $options); + } + catch (FacebookException $e) + { + //TODO: Exception handling + } + + $token_array = array(); + parse_str($token_reply, $token_array); + + if (array_key_exists('access_token', $token_array)) + { + $this->_token = $token_array['access_token']; + } + else + { + //TODO: Exception handling + } + + $this->_CI->load->helper('array'); + $this->_expires = element('expires', $token_array, 0); + + // set session storage to newly acquired token + $this->_CI->session->set_userdata(array('fb_token' => $this->_token, 'fb_token_expires' => $this->_expires)); + + redirect($this->_callback); + } + + private function _base64UrlDecode($input) + { + return base64_decode(strtr($input, '-_', '+/')); + } + + /** + * If $_GET['code'] is set, get an access token + * ElseIf $_GET['error'] is set, return false + * Else redirect to login URL + * @return bool + */ + public function login() + { + redirect($this->get_loginURL()); + } + + /** + * Unset session data and local token variable + */ + public function logout() + { + $this->_token = ''; + $this->_CI->session->unset_userdata('fb_token'); + $this->_CI->session->unset_userdata('fb_token_expires'); + } + + /** + * Check if token is set + * @return bool + */ + public function logged_in() + { + if (($code = $this->_CI->input->get('code')) !== false) + { + $this->_authorize($code); + } + elseif (($error = $this->_CI->input->get('error')) !== false) + { + throw new FacebookException($this->_CI->input->get('error').$this->_CI->input->get('error_description'), 401); + } + + // get token from cookie + $cookie_token = $this->_CI->session->userdata('fb_token'); + // check if token is empty (both local and cookie) + $empty_token = empty($this->_token) && empty($cookie_token); + // get expiry from cookie + $cookie_expiry = $this->_CI->session->userdata('fb_token_expires'); + // check if token expires at all + $token_expires = $this->_expires > 0 || $cookie_expiry > 0; + + // Token empty, login required + if ($empty_token) + { + return false; + } + elseif ($token_expires) + { + // check if token is still valid + $token_valid = ($this->_expires > time()) || ($cookie_expiry && ($cookie_expiry > time())); + + // return token_valid state + return $token_valid; + } + else + { + // token exists and never expires + return true; + } + } + + /** + * Retrieve Access Token for external storage + * @return string + */ + public function get_token() + { + return $this->_token; + } + + /** + * Set Access Token from external source + * @param string $token + */ + public function set_token($token) + { + $this->_token = $token; + } + + /** + * Issue API call via GET/POST (request_method) + * + * @param string $request_method + * @param string $uri + * @param array $params + * @return mixed + */ + public function call($request_method, $uri, array $params = NULL) + { + $this->_initCurl(); + // Set the Query URL to the requested option + $apiURL = $this->_apiURL.$uri.'?access_token='.$this->_token; + // build a querystring from the parameters array + $paramString = $params === NULL ? '' : http_build_query($params); + + if ($request_method === 'post') + { + curl_setopt($this->_ch, CURLOPT_POST, TRUE); + curl_setopt($this->_ch, CURLOPT_POSTFIELDS, $paramString); + } + else + { + $apiURL .= '&'.$paramString; + } + + + curl_setopt($this->_ch, CURLOPT_URL, $apiURL); + $response = curl_exec($this->_ch); + + // Error validating Certificate Chain, use bundled CA info + if (curl_errno($this->_ch) == CURLE_SSL_CACERT) + { + curl_setopt($this->_ch, CURLOPT_CAINFO, dirname(__FILE__).'/fb_ca_chain_bundle.crt'); + $response = curl_exec($this->_ch); + } + + // cURL Error + if ($response === false) + { + $e = new FacebookException(curl_error($this->_ch, curl_errno($this->_ch))); + curl_close($this->_ch); + + throw $e; + } + + curl_close($this->_ch); + + if (($decoded_response = json_decode($response)) == NULL) + { + return $response; + } + else + { + return $decoded_response; + } + } + + /** + * Retrieve URL to Login dialog + * @return string + */ + public function get_loginURL() + { + return 'https://www.facebook.com/dialog/oauth?client_id='.$this->_appId + .'&redirect_uri='.urlencode($this->_callback) + .'&scope='.$this->_CI->config->item('facebook_default_scope'); + } + + /** + * Set Callback URL + * @param string $url + */ + public function set_callback($url) + { + $this->_callback = $url; + } + + /** + * Log in as the application + */ + public function app_login() + { + $options = array + ( + 'client_id' => $this->_appId, + 'client_secret' => $this->_appSecret, + 'grant_type' => 'client_credentials', + ); + + try + { + $token_response = $this->call('get', 'oauth/access_token', $options); + } + catch(FacebookException $e) + { + //TODO: Exception Handling + } + + // For app login only set local variables, so user experience is not permanently changed + $this->_token = $token_response['access_token']; + $this->_expires = 0; + } + + public function parse_signedRequest($signedRequest) + { + list($encoded_sig, $payload) = explode('.', $signedRequest, 2); + // decode the data + $sig = $this->base64UrlDecode($encoded_sig); + $data = json_decode($this->base64UrlDecode($payload), true); + + if (strtoupper($data['algorithm']) !== 'HMAC-SHA256') + { + log_message('error', 'Unknown algorithm. Expected HMAC-SHA256'); + log_message('debug', 'Bad signature algorithm: '.$data['algorithm']); + return null; + } + + // check sig + $expected_sig = hash_hmac('sha256', $payload, $this->_appSecret, $raw = true); + if ($sig !== $expected_sig) + { + log_message('debug', 'Bad Signed JSON signature!'); + return null; + } + return $data; + } +} + + +/** + * + * Enter description here ... + * @author Benedikt Bauer + * + */ +class FacebookException extends Exception +{ + //TODO - Insert your code here +} + +/* End of File: facebook_api2.php */ +/* Location: ./application/libraries/facebook_api2.php */ \ No newline at end of file diff --git a/application/libraries/fb_ca_chain_bundle.crt b/application/libraries/fb_ca_chain_bundle.crt new file mode 100644 index 0000000..e69de29 diff --git a/application/views/facebook_view.php b/application/views/facebook_view.php index 42845ce..5453a8d 100644 --- a/application/views/facebook_view.php +++ b/application/views/facebook_view.php @@ -50,15 +50,15 @@
- facebook->logged_in() ): ?> - Login + facebook_api->logged_in() ): ?> + Login - facebook->user();?> + facebook_api->user();?>

name?> ( Logout )

- facebook->call('get', 'me', array('metadata' => 1));?> + facebook_api->call('get', 'me', array('metadata' => 1));?>