diff --git a/e107_handlers/hybridauth/Hybrid/Auth.php b/e107_handlers/hybridauth/Hybrid/Auth.php index a388ccfb4..9e094e5f3 100644 --- a/e107_handlers/hybridauth/Hybrid/Auth.php +++ b/e107_handlers/hybridauth/Hybrid/Auth.php @@ -15,7 +15,7 @@ */ class Hybrid_Auth { - public static $version = "2.6.0"; + public static $version = "2.8.0"; /** * Configuration array @@ -352,6 +352,9 @@ class Hybrid_Auth { * @param string $mode PHP|JS */ public static function redirect($url, $mode = "PHP") { + if(!$mode){ + $mode = 'PHP'; + } Hybrid_Logger::info("Enter Hybrid_Auth::redirect( $url, $mode )"); // Ensure session is saved before sending response, see https://github.com/symfony/symfony/pull/12341 diff --git a/e107_handlers/hybridauth/Hybrid/Endpoint.php b/e107_handlers/hybridauth/Hybrid/Endpoint.php index fbb1a40a7..7813fee05 100644 --- a/e107_handlers/hybridauth/Hybrid/Endpoint.php +++ b/e107_handlers/hybridauth/Hybrid/Endpoint.php @@ -27,7 +27,7 @@ class Hybrid_Endpoint { // with /index.php?hauth.done={provider}?{args}... // >here we need to parse $_SERVER[QUERY_STRING] $request = $_REQUEST; - if (strrpos($_SERVER["QUERY_STRING"], '?')) { + if (isset($_SERVER["QUERY_STRING"]) && strrpos($_SERVER["QUERY_STRING"], '?')) { $_SERVER["QUERY_STRING"] = str_replace("?", "&", $_SERVER["QUERY_STRING"]); parse_str($_SERVER["QUERY_STRING"], $request); } diff --git a/e107_handlers/hybridauth/Hybrid/Provider_Adapter.php b/e107_handlers/hybridauth/Hybrid/Provider_Adapter.php index 7809f6a82..30db81d74 100644 --- a/e107_handlers/hybridauth/Hybrid/Provider_Adapter.php +++ b/e107_handlers/hybridauth/Hybrid/Provider_Adapter.php @@ -153,11 +153,26 @@ class Hybrid_Provider_Adapter { # for default HybridAuth endpoint url hauth_login_start_url # auth.start required the IDp ID # auth.time optional login request timestamp - $this->params["login_start"] = $HYBRID_AUTH_URL_BASE . ( strpos($HYBRID_AUTH_URL_BASE, '?') ? '&' : '?' ) . "hauth.start={$this->id}&hauth.time={$this->params["hauth_time"]}"; + if (!isset($this->params["login_start"]) ) { + $this->params["login_start"] = $HYBRID_AUTH_URL_BASE . ( strpos($HYBRID_AUTH_URL_BASE, '?') ? '&' : '?' ) . "hauth.start={$this->id}&hauth.time={$this->params["hauth_time"]}"; + } # for default HybridAuth endpoint url hauth_login_done_url # auth.done required the IDp ID - $this->params["login_done"] = $HYBRID_AUTH_URL_BASE . ( strpos($HYBRID_AUTH_URL_BASE, '?') ? '&' : '?' ) . "hauth.done={$this->id}"; + if (!isset($this->params["login_done"]) ) { + $this->params["login_done"] = $HYBRID_AUTH_URL_BASE . ( strpos($HYBRID_AUTH_URL_BASE, '?') ? '&' : '?' ) . "hauth.done={$this->id}"; + } + + # workaround to solve windows live authentication since microsoft disallowed redirect urls to contain any parameters + # http://mywebsite.com/path_to_hybridauth/?hauth.done=Live will not work + if ($this->id=="Live") { + $this->params["login_done"] = $HYBRID_AUTH_URL_BASE."live.php"; + } + + # Workaround to fix broken callback urls for the Facebook OAuth client + if ($this->adapter->useSafeUrls) { + $this->params['login_done'] = str_replace('hauth.done', 'hauth_done', $this->params['login_done']); + } if (isset($this->params["hauth_return_to"])) { Hybrid_Auth::storage()->set("hauth_session.{$this->id}.hauth_return_to", $this->params["hauth_return_to"]); @@ -173,7 +188,12 @@ class Hybrid_Provider_Adapter { // move on Hybrid_Logger::debug("Hybrid_Provider_Adapter::login( {$this->id} ), redirect the user to login_start URL."); - Hybrid_Auth::redirect($this->params["login_start"]); + // redirect + if (empty($this->params["redirect_mode"])) { + Hybrid_Auth::redirect($this->params["login_start"]); + } else { + Hybrid_Auth::redirect($this->params["login_start"],$this->params["redirect_mode"]); + } } /** @@ -281,6 +301,12 @@ class Hybrid_Provider_Adapter { // get the stored callback url $callback_url = Hybrid_Auth::storage()->get("hauth_session.{$this->id}.hauth_return_to"); + // if the user presses the back button in the browser and we already deleted the hauth_return_to from + // the session in the previous request, we will redirect to '/' instead of displaying a blank page. + if (!$callback_url) { + $callback_url = '/'; + } + // remove some unneeded stored data Hybrid_Auth::storage()->delete("hauth_session.{$this->id}.hauth_return_to"); Hybrid_Auth::storage()->delete("hauth_session.{$this->id}.hauth_endpoint"); diff --git a/e107_handlers/hybridauth/Hybrid/Provider_Model.php b/e107_handlers/hybridauth/Hybrid/Provider_Model.php index d044bd540..a84d618ab 100644 --- a/e107_handlers/hybridauth/Hybrid/Provider_Model.php +++ b/e107_handlers/hybridauth/Hybrid/Provider_Model.php @@ -64,6 +64,9 @@ abstract class Hybrid_Provider_Model { */ public $compressed = false; + /** @var bool $useSafeUrls Enable this to replace '.' with '_' characters in the callback urls */ + public $useSafeUrls = false; + /** * Common providers adapter constructor * diff --git a/e107_handlers/hybridauth/Hybrid/Providers/Amazon.php b/e107_handlers/hybridauth/Hybrid/Providers/Amazon.php new file mode 100644 index 000000000..cc7d14ce5 --- /dev/null +++ b/e107_handlers/hybridauth/Hybrid/Providers/Amazon.php @@ -0,0 +1,85 @@ +config['keys']['id'] || ! $this->config['keys']['secret'] ) { + throw new Exception( "Your application id and secret are required in order to connect to {$this->providerId}.", 4 ); + } + + // override requested scope + if ( isset( $this->config['scope'] ) && ! empty( $this->config['scope'] ) ) { + $this->scope = $this->config['scope']; + } + + // include OAuth2 client + require_once Hybrid_Auth::$config['path_libraries'] . 'OAuth/OAuth2Client.php'; + require_once Hybrid_Auth::$config['path_libraries'] . 'Amazon/AmazonOAuth2Client.php'; + + // create a new OAuth2 client instance + $this->api = new AmazonOAuth2Client( $this->config['keys']['id'], $this->config['keys']['secret'], $this->endpoint, $this->compressed ); + + $this->api->api_base_url = 'https://api.amazon.com'; + $this->api->authorize_url = 'https://www.amazon.com/ap/oa'; + $this->api->token_url = 'https://api.amazon.com/auth/o2/token'; + + $this->api->curl_header = array( 'Content-Type: application/x-www-form-urlencoded' ); + + // If we have an access token, set it + if ( $this->token( 'access_token' ) ) { + $this->api->access_token = $this->token('access_token'); + $this->api->refresh_token = $this->token('refresh_token'); + $this->api->access_token_expires_in = $this->token('expires_in'); + $this->api->access_token_expires_at = $this->token('expires_at'); + } + + // Set curl proxy if exists + if ( isset( Hybrid_Auth::$config['proxy'] ) ) { + $this->api->curl_proxy = Hybrid_Auth::$config['proxy']; + } + } + + /** + * load the user profile from the IDp api client + */ + function getUserProfile() { + + $data = $this->api->get( '/user/profile' ); + + if ( ! isset( $data->user_id ) ){ + throw new Exception( "User profile request failed! {$this->providerId} returned an invalid response.", 6 ); + } + + $this->user->profile->identifier = @ $data->user_id; + $this->user->profile->email = @ $data->email; + $this->user->profile->displayName = @ $data->name; + $this->user->profile->zip = @ $data->postal_code; + + return $this->user->profile; + } +} diff --git a/e107_handlers/hybridauth/Hybrid/Providers/Facebook.php b/e107_handlers/hybridauth/Hybrid/Providers/Facebook.php index bd05baeeb..ca6029480 100644 --- a/e107_handlers/hybridauth/Hybrid/Providers/Facebook.php +++ b/e107_handlers/hybridauth/Hybrid/Providers/Facebook.php @@ -1,5 +1,8 @@ config["keys"]["id"] || !$this->config["keys"]["secret"]) { - throw new Exception("Your application id and secret are required in order to connect to {$this->providerId}.", 4); - } - - if (!class_exists('FacebookApiException', false)) { - require_once Hybrid_Auth::$config["path_libraries"] . "Facebook/base_facebook.php"; - require_once Hybrid_Auth::$config["path_libraries"] . "Facebook/facebook.php"; - } - - if (isset(Hybrid_Auth::$config["proxy"])) { - BaseFacebook::$CURL_OPTS[CURLOPT_PROXY] = Hybrid_Auth::$config["proxy"]; - } - - $trustForwarded = isset($this->config['trustForwarded']) ? (bool) $this->config['trustForwarded'] : false; - $this->api = new Facebook(array('appId' => $this->config["keys"]["id"], 'secret' => $this->config["keys"]["secret"], 'trustForwarded' => $trustForwarded)); - - if ($this->token("access_token")) { - $this->api->setAccessToken($this->token("access_token")); - $this->api->setExtendedAccessToken(); - $access_token = $this->api->getAccessToken(); - - if ($access_token) { - $this->token("access_token", $access_token); - $this->api->setAccessToken($access_token); - } - - $this->api->setAccessToken($this->token("access_token")); - } - - $this->api->getUser(); - } - - /** - * {@inheritdoc} - */ - function loginBegin() { - $parameters = array("scope" => $this->scope, "redirect_uri" => $this->endpoint, "display" => "page"); - $optionals = array("scope", "redirect_uri", "display", "auth_type"); - - foreach ($optionals as $parameter) { - if (isset($this->config[$parameter]) && !empty($this->config[$parameter])) { - $parameters[$parameter] = $this->config[$parameter]; - - //If the auth_type parameter is used, we need to generate a nonce and include it as a parameter - if ($parameter == "auth_type") { - $nonce = md5(uniqid(mt_rand(), true)); - $parameters['auth_nonce'] = $nonce; - - Hybrid_Auth::storage()->set('fb_auth_nonce', $nonce); - } - } - } - - if (isset($this->config['force']) && $this->config['force'] === true) { - $parameters['auth_type'] = 'reauthenticate'; - $parameters['auth_nonce'] = md5(uniqid(mt_rand(), true)); - - Hybrid_Auth::storage()->set('fb_auth_nonce', $parameters['auth_nonce']); - } - - // get the login url - $url = $this->api->getLoginUrl($parameters); - - // redirect to facebook - Hybrid_Auth::redirect($url); - } - - /** - * {@inheritdoc} - */ - function loginFinish() { - // in case we get error_reason=user_denied&error=access_denied - if (isset($_REQUEST['error']) && $_REQUEST['error'] == "access_denied") { - throw new Exception("Authentication failed! The user denied your request.", 5); - } - - // in case we are using iOS/Facebook reverse authentication - if (isset($_REQUEST['access_token'])) { - $this->token("access_token", $_REQUEST['access_token']); - $this->api->setAccessToken($this->token("access_token")); - $this->api->setExtendedAccessToken(); - $access_token = $this->api->getAccessToken(); - - if ($access_token) { - $this->token("access_token", $access_token); - $this->api->setAccessToken($access_token); - } - - $this->api->setAccessToken($this->token("access_token")); - } - - - // if auth_type is used, then an auth_nonce is passed back, and we need to check it. - if (isset($_REQUEST['auth_nonce'])) { - - $nonce = Hybrid_Auth::storage()->get('fb_auth_nonce'); - - //Delete the nonce - Hybrid_Auth::storage()->delete('fb_auth_nonce'); - - if ($_REQUEST['auth_nonce'] != $nonce) { - throw new Exception("Authentication failed! Invalid nonce used for reauthentication.", 5); - } - } - - // try to get the UID of the connected user from fb, should be > 0 - if (!$this->api->getUser()) { - throw new Exception("Authentication failed! {$this->providerId} returned an invalid user id.", 5); - } - - // set user as logged in - $this->setUserConnected(); - - // store facebook access token - $this->token("access_token", $this->api->getAccessToken()); - } - - /** - * {@inheritdoc} - */ - function logout() { - $this->api->destroySession(); - parent::logout(); - } - - /** - * {@inheritdoc} - */ - function getUserProfile() { - // request user profile from fb api - try { - $fields = array( - 'id', 'name', 'first_name', 'last_name', 'link', 'website', - 'gender', 'locale', 'about', 'email', 'hometown', 'location', - 'birthday' - ); - - $data = $this->api->api('/me?fields=' . implode(',', $fields)); - } catch (FacebookApiException $e) { - throw new Exception("User profile request failed! {$this->providerId} returned an error: {$e->getMessage()}", 6, $e); - } - - // if the provider identifier is not received, we assume the auth has failed - if (!isset($data["id"])) { - throw new Exception("User profile request failed! {$this->providerId} api returned an invalid response: " . Hybrid_Logger::dumpData( $data ), 6); - } - - # store the user profile. - $this->user->profile->identifier = (array_key_exists('id', $data)) ? $data['id'] : ""; - $this->user->profile->username = (array_key_exists('username', $data)) ? $data['username'] : ""; - $this->user->profile->displayName = (array_key_exists('name', $data)) ? $data['name'] : ""; - $this->user->profile->firstName = (array_key_exists('first_name', $data)) ? $data['first_name'] : ""; - $this->user->profile->lastName = (array_key_exists('last_name', $data)) ? $data['last_name'] : ""; - $this->user->profile->photoURL = "https://graph.facebook.com/" . $this->user->profile->identifier . "/picture?width=150&height=150"; - $this->user->profile->coverInfoURL = "https://graph.facebook.com/" . $this->user->profile->identifier . "?fields=cover&access_token=" . $this->api->getAccessToken(); - $this->user->profile->profileURL = (array_key_exists('link', $data)) ? $data['link'] : ""; - $this->user->profile->webSiteURL = (array_key_exists('website', $data)) ? $data['website'] : ""; - $this->user->profile->gender = (array_key_exists('gender', $data)) ? $data['gender'] : ""; - $this->user->profile->language = (array_key_exists('locale', $data)) ? $data['locale'] : ""; - $this->user->profile->description = (array_key_exists('about', $data)) ? $data['about'] : ""; - $this->user->profile->email = (array_key_exists('email', $data)) ? $data['email'] : ""; - $this->user->profile->emailVerified = (array_key_exists('email', $data)) ? $data['email'] : ""; - $this->user->profile->region = (array_key_exists("location", $data) && array_key_exists("name", $data['location'])) ? $data['location']["name"] : ""; - - if (!empty($this->user->profile->region)) { - $regionArr = explode(',', $this->user->profile->region); - if (count($regionArr) > 1) { - $this->user->profile->city = trim($regionArr[0]); - $this->user->profile->country = trim($regionArr[1]); - } - } - - if (array_key_exists('birthday', $data)) { - list($birthday_month, $birthday_day, $birthday_year) = explode("/", $data['birthday']); - - $this->user->profile->birthDay = (int) $birthday_day; - $this->user->profile->birthMonth = (int) $birthday_month; - $this->user->profile->birthYear = (int) $birthday_year; - } - - return $this->user->profile; - } - - /** - * Attempt to retrieve the url to the cover image given the coverInfoURL - * - * @param string $coverInfoURL coverInfoURL variable - * @return string url to the cover image OR blank string - */ - function getCoverURL($coverInfoURL) { - try { - $headers = get_headers($coverInfoURL); - if (substr($headers[0], 9, 3) != "404") { - $coverOBJ = json_decode(file_get_contents($coverInfoURL)); - if (array_key_exists('cover', $coverOBJ)) { - return $coverOBJ->cover->source; - } - } - } catch (Exception $e) { - - } - - return ""; - } - - /** - * {@inheritdoc} - */ - function getUserContacts() { - $apiCall = '?fields=link,name'; - $returnedContacts = array(); - $pagedList = false; - - do { - try { - $response = $this->api->api('/me/friends' . $apiCall); - } catch (FacebookApiException $e) { - throw new Exception("User contacts request failed! {$this->providerId} returned an error {$e->getMessage()}", 0, $e); - } - - // Prepare the next call if paging links have been returned - if (array_key_exists('paging', $response) && array_key_exists('next', $response['paging'])) { - $pagedList = true; - $next_page = explode('friends', $response['paging']['next']); - $apiCall = $next_page[1]; - } else { - $pagedList = false; - } - - // Add the new page contacts - $returnedContacts = array_merge($returnedContacts, $response['data']); - } while ($pagedList == true); - - $contacts = array(); - - foreach ($returnedContacts as $item) { - - $uc = new Hybrid_User_Contact(); - $uc->identifier = (array_key_exists("id", $item)) ? $item["id"] : ""; - $uc->displayName = (array_key_exists("name", $item)) ? $item["name"] : ""; - $uc->profileURL = (array_key_exists("link", $item)) ? $item["link"] : "https://www.facebook.com/profile.php?id=" . $uc->identifier; - $uc->photoURL = "https://graph.facebook.com/" . $uc->identifier . "/picture?width=150&height=150"; - - $contacts[] = $uc; - } - - return $contacts; - } - - /** - * Update user status - * - * @param mixed $status An array describing the status, or string - * @param string $pageid (optional) User page id - * @return array - * @throw Exception - */ - function setUserStatus($status, $pageid = null) { - if (!is_array($status)) { - $status = array('message' => $status); - } - - if (is_null($pageid)) { - $pageid = 'me'; - - // if post on page, get access_token page - } else { - $access_token = null; - foreach ($this->getUserPages(true) as $p) { - if (isset($p['id']) && intval($p['id']) == intval($pageid)) { - $access_token = $p['access_token']; - break; - } - } - - if (is_null($access_token)) { - throw new Exception("Update user page failed, page not found or not writable!"); - } - - $status['access_token'] = $access_token; - } - - try { - $response = $this->api->api('/' . $pageid . '/feed', 'post', $status); - } catch (FacebookApiException $e) { - throw new Exception("Update user status failed! {$this->providerId} returned an error {$e->getMessage()}", 0, $e); - } - - return $response; - } - - /** - * {@inheridoc} - */ - function getUserStatus($postid) { - try { - $postinfo = $this->api->api("/" . $postid); - } catch (FacebookApiException $e) { - throw new Exception("Cannot retrieve user status! {$this->providerId} returned an error: {$e->getMessage()}", 0, $e); - } - - return $postinfo; - } - - /** - * {@inheridoc} - */ - function getUserPages($writableonly = false) { - if (( isset($this->config['scope']) && strpos($this->config['scope'], 'manage_pages') === false ) || (!isset($this->config['scope']) && strpos($this->scope, 'manage_pages') === false )) - throw new Exception("User status requires manage_page permission!"); - - try { - $pages = $this->api->api("/me/accounts", 'get'); - } catch (FacebookApiException $e) { - throw new Exception("Cannot retrieve user pages! {$this->providerId} returned an error: {$e->getMessage()}", 0, $e); - } - - if (!isset($pages['data'])) { - return array(); - } - - if (!$writableonly) { - return $pages['data']; - } - - $wrpages = array(); - foreach ($pages['data'] as $p) { - if (isset($p['perms']) && in_array('CREATE_CONTENT', $p['perms'])) { - $wrpages[] = $p; - } - } - - return $wrpages; - } - - /** - * load the user latest activity - * - timeline : all the stream - * - me : the user activity only - * {@inheritdoc} - */ - function getUserActivity($stream) { - try { - if ($stream == "me") { - $response = $this->api->api('/me/feed'); - } else { - $response = $this->api->api('/me/home'); - } - } catch (FacebookApiException $e) { - throw new Exception("User activity stream request failed! {$this->providerId} returned an error: {$e->getMessage()}", 0, $e); - } - - if (!$response || !count($response['data'])) { - return array(); - } - - $activities = array(); - - foreach ($response['data'] as $item) { - if ($stream == "me" && $item["from"]["id"] != $this->api->getUser()) { - continue; - } - - $ua = new Hybrid_User_Activity(); - - $ua->id = (array_key_exists("id", $item)) ? $item["id"] : ""; - $ua->date = (array_key_exists("created_time", $item)) ? strtotime($item["created_time"]) : ""; - - if ($item["type"] == "video") { - $ua->text = (array_key_exists("link", $item)) ? $item["link"] : ""; - } - - if ($item["type"] == "link") { - $ua->text = (array_key_exists("link", $item)) ? $item["link"] : ""; - } - - if (empty($ua->text) && isset($item["story"])) { - $ua->text = (array_key_exists("link", $item)) ? $item["link"] : ""; - } - - if (empty($ua->text) && isset($item["message"])) { - $ua->text = (array_key_exists("message", $item)) ? $item["message"] : ""; - } - - if (!empty($ua->text)) { - $ua->user->identifier = (array_key_exists("id", $item["from"])) ? $item["from"]["id"] : ""; - $ua->user->displayName = (array_key_exists("name", $item["from"])) ? $item["from"]["name"] : ""; - $ua->user->profileURL = "https://www.facebook.com/profile.php?id=" . $ua->user->identifier; - $ua->user->photoURL = "https://graph.facebook.com/" . $ua->user->identifier . "/picture?type=square"; - - $activities[] = $ua; - } - } - - return $activities; - } + /** + * Default permissions, and a lot of them. You can change them from the configuration by setting the scope to what you want/need. + * For a complete list see: https://developers.facebook.com/docs/facebook-login/permissions + * + * @link https://developers.facebook.com/docs/facebook-login/permissions + * @var array $scope + */ + public $scope = ['email', 'user_about_me', 'user_birthday', 'user_hometown', 'user_location', 'user_website', 'publish_actions', 'read_custom_friendlists']; + + /** + * Provider API client + * + * @var \Facebook\Facebook + */ + public $api; + + public $useSafeUrls = true; + + /** + * {@inheritdoc} + */ + function initialize() { + if (!$this->config["keys"]["id"] || !$this->config["keys"]["secret"]) { + throw new Exception("Your application id and secret are required in order to connect to {$this->providerId}.", 4); + } + + if (isset($this->config['scope'])) { + $scope = $this->config['scope']; + if (is_string($scope)) { + $scope = explode(",", $scope); + } + $scope = array_map('trim', $scope); + $this->scope = $scope; + } + + $trustForwarded = isset($this->config['trustForwarded']) ? (bool)$this->config['trustForwarded'] : false; + + $this->api = new FacebookSDK([ + 'app_id' => $this->config["keys"]["id"], + 'app_secret' => $this->config["keys"]["secret"], + 'default_graph_version' => 'v2.8', + 'trustForwarded' => $trustForwarded, + ]); + } + + /** + * {@inheritdoc} + */ + function loginBegin() { + + $this->endpoint = $this->params['login_done']; + $helper = $this->api->getRedirectLoginHelper(); + + // Use re-request, because this will trigger permissions window if not all permissions are granted. + $url = $helper->getReRequestUrl($this->endpoint, $this->scope); + + // Redirect to Facebook + Hybrid_Auth::redirect($url); + } + + /** + * {@inheritdoc} + */ + function loginFinish() { + + $helper = $this->api->getRedirectLoginHelper(); + try { + $accessToken = $helper->getAccessToken(); + } catch (Facebook\Exceptions\FacebookResponseException $e) { + throw new Hybrid_Exception('Facebook Graph returned an error: ' . $e->getMessage()); + } catch (Facebook\Exceptions\FacebookSDKException $e) { + throw new Hybrid_Exception('Facebook SDK returned an error: ' . $e->getMessage()); + } + + if (!isset($accessToken)) { + if ($helper->getError()) { + throw new Hybrid_Exception(sprintf("Could not authorize user, reason: %s (%d)", $helper->getErrorDescription(), $helper->getErrorCode())); + } else { + throw new Hybrid_Exception("Could not authorize user. Bad request"); + } + } + + try { + // Validate token + $oAuth2Client = $this->api->getOAuth2Client(); + $tokenMetadata = $oAuth2Client->debugToken($accessToken); + $tokenMetadata->validateAppId($this->config["keys"]["id"]); + $tokenMetadata->validateExpiration(); + + // Exchanges a short-lived access token for a long-lived one + if (!$accessToken->isLongLived()) { + $accessToken = $oAuth2Client->getLongLivedAccessToken($accessToken); + } + } catch (FacebookSDKException $e) { + throw new Hybrid_Exception($e->getMessage(), 0, $e); + } + + $this->setUserConnected(); + $this->token("access_token", $accessToken->getValue()); + } + + /** + * {@inheritdoc} + */ + function logout() { + parent::logout(); + } + + /** + * {@inheritdoc} + */ + function getUserProfile() { + try { + $fields = [ + 'id', + 'name', + 'first_name', + 'last_name', + 'link', + 'website', + 'gender', + 'locale', + 'about', + 'email', + 'hometown', + 'location', + 'birthday' + ]; + $response = $this->api->get('/me?fields=' . implode(',', $fields), $this->token('access_token')); + $data = $response->getDecodedBody(); + } catch (FacebookSDKException $e) { + throw new Exception("User profile request failed! {$this->providerId} returned an error: {$e->getMessage()}", 6, $e); + } + + // Store the user profile. + $this->user->profile->identifier = (array_key_exists('id', $data)) ? $data['id'] : ""; + $this->user->profile->displayName = (array_key_exists('name', $data)) ? $data['name'] : ""; + $this->user->profile->firstName = (array_key_exists('first_name', $data)) ? $data['first_name'] : ""; + $this->user->profile->lastName = (array_key_exists('last_name', $data)) ? $data['last_name'] : ""; + $this->user->profile->photoURL = !empty($this->user->profile->identifier) ? "https://graph.facebook.com/" . $this->user->profile->identifier . "/picture?width=150&height=150" : ''; + $this->user->profile->profileURL = (array_key_exists('link', $data)) ? $data['link'] : ""; + $this->user->profile->webSiteURL = (array_key_exists('website', $data)) ? $data['website'] : ""; + $this->user->profile->gender = (array_key_exists('gender', $data)) ? $data['gender'] : ""; + $this->user->profile->language = (array_key_exists('locale', $data)) ? $data['locale'] : ""; + $this->user->profile->description = (array_key_exists('about', $data)) ? $data['about'] : ""; + $this->user->profile->email = (array_key_exists('email', $data)) ? $data['email'] : ""; + $this->user->profile->emailVerified = (array_key_exists('email', $data)) ? $data['email'] : ""; + $this->user->profile->region = (array_key_exists("location", $data) && array_key_exists("name", $data['location'])) ? $data['location']["name"] : ""; + + if (!empty($this->user->profile->region)) { + $regionArr = explode(',', $this->user->profile->region); + if (count($regionArr) > 1) { + $this->user->profile->city = trim($regionArr[0]); + $this->user->profile->country = trim($regionArr[1]); + } + } + + if (array_key_exists('birthday', $data)) { + $birtydayPieces = explode('/', $data['birthday']); + + if (count($birtydayPieces) == 1) { + $this->user->profile->birthYear = (int)$birtydayPieces[0]; + } elseif (count($birtydayPieces) == 2) { + $this->user->profile->birthMonth = (int)$birtydayPieces[0]; + $this->user->profile->birthDay = (int)$birtydayPieces[1]; + } elseif (count($birtydayPieces) == 3) { + $this->user->profile->birthMonth = (int)$birtydayPieces[0]; + $this->user->profile->birthDay = (int)$birtydayPieces[1]; + $this->user->profile->birthYear = (int)$birtydayPieces[2]; + } + } + + return $this->user->profile; + } + + /** + * Since the Graph API 2.0, the /friends endpoint only returns friend that also use your Facebook app. + * {@inheritdoc} + */ + function getUserContacts() { + $apiCall = '?fields=link,name'; + $returnedContacts = []; + $pagedList = true; + + while ($pagedList) { + try { + $response = $this->api->get('/me/friends' . $apiCall, $this->token('access_token')); + $response = $response->getDecodedBody(); + } catch (FacebookSDKException $e) { + throw new Hybrid_Exception("User contacts request failed! {$this->providerId} returned an error {$e->getMessage()}", 0, $e); + } + + // Prepare the next call if paging links have been returned + if (array_key_exists('paging', $response) && array_key_exists('next', $response['paging'])) { + $pagedList = true; + $next_page = explode('friends', $response['paging']['next']); + $apiCall = $next_page[1]; + } else { + $pagedList = false; + } + + // Add the new page contacts + $returnedContacts = array_merge($returnedContacts, $response['data']); + } + + $contacts = []; + + foreach ($returnedContacts as $item) { + + $uc = new Hybrid_User_Contact(); + $uc->identifier = (array_key_exists("id", $item)) ? $item["id"] : ""; + $uc->displayName = (array_key_exists("name", $item)) ? $item["name"] : ""; + $uc->profileURL = (array_key_exists("link", $item)) ? $item["link"] : "https://www.facebook.com/profile.php?id=" . $uc->identifier; + $uc->photoURL = "https://graph.facebook.com/" . $uc->identifier . "/picture?width=150&height=150"; + + $contacts[] = $uc; + } + + return $contacts; + } + + /** + * Load the user latest activity, needs 'read_stream' permission + * + * @param string $stream Which activity to fetch: + * - timeline : all the stream + * - me : the user activity only + * {@inheritdoc} + */ + function getUserActivity($stream = 'timeline') { + try { + if ($stream == "me") { + $response = $this->api->get('/me/feed', $this->token('access_token')); + } else { + $response = $this->api->get('/me/home', $this->token('access_token')); + } + } catch (FacebookSDKException $e) { + throw new Hybrid_Exception("User activity stream request failed! {$this->providerId} returned an error: {$e->getMessage()}", 0, $e); + } + + if (!$response || !count($response['data'])) { + return []; + } + + $activities = []; + + foreach ($response['data'] as $item) { + + $ua = new Hybrid_User_Activity(); + + $ua->id = (array_key_exists("id", $item)) ? $item["id"] : ""; + $ua->date = (array_key_exists("created_time", $item)) ? strtotime($item["created_time"]) : ""; + + if ($item["type"] == "video") { + $ua->text = (array_key_exists("link", $item)) ? $item["link"] : ""; + } + + if ($item["type"] == "link") { + $ua->text = (array_key_exists("link", $item)) ? $item["link"] : ""; + } + + if (empty($ua->text) && isset($item["story"])) { + $ua->text = (array_key_exists("link", $item)) ? $item["link"] : ""; + } + + if (empty($ua->text) && isset($item["message"])) { + $ua->text = (array_key_exists("message", $item)) ? $item["message"] : ""; + } + + if (!empty($ua->text)) { + $ua->user->identifier = (array_key_exists("id", $item["from"])) ? $item["from"]["id"] : ""; + $ua->user->displayName = (array_key_exists("name", $item["from"])) ? $item["from"]["name"] : ""; + $ua->user->profileURL = "https://www.facebook.com/profile.php?id=" . $ua->user->identifier; + $ua->user->photoURL = "https://graph.facebook.com/" . $ua->user->identifier . "/picture?type=square"; + + $activities[] = $ua; + } + } + + return $activities; + } } diff --git a/e107_handlers/hybridauth/Hybrid/Providers/Google.php b/e107_handlers/hybridauth/Hybrid/Providers/Google.php index dd0127006..3644493fb 100644 --- a/e107_handlers/hybridauth/Hybrid/Providers/Google.php +++ b/e107_handlers/hybridauth/Hybrid/Providers/Google.php @@ -158,12 +158,20 @@ class Hybrid_Providers_Google extends Hybrid_Provider_Model_OAuth2 { } else { $this->user->profile->webSiteURL = ''; } - // google API returns age ranges or min. age only (with plus.login scope) + // google API returns age ranges min and/or max as of https://developers.google.com/+/web/api/rest/latest/people#resource if (property_exists($response, 'ageRange')) { if (property_exists($response->ageRange, 'min') && property_exists($response->ageRange, 'max')) { $this->user->profile->age = $response->ageRange->min . ' - ' . $response->ageRange->max; } else { - $this->user->profile->age = '> ' . $response->ageRange->min; + if (property_exists($response->ageRange, 'min')) { + $this->user->profile->age = '>= ' . $response->ageRange->min; + } else { + if (property_exists($response->ageRange, 'max')) { + $this->user->profile->age = '<= ' . $response->ageRange->max; + } else { + $this->user->profile->age = ''; + } + } } } else { $this->user->profile->age = ''; diff --git a/e107_handlers/hybridauth/Hybrid/Providers/LinkedIn.php b/e107_handlers/hybridauth/Hybrid/Providers/LinkedIn.php index 73467edb4..6d3438ebf 100644 --- a/e107_handlers/hybridauth/Hybrid/Providers/LinkedIn.php +++ b/e107_handlers/hybridauth/Hybrid/Providers/LinkedIn.php @@ -17,7 +17,7 @@ class Hybrid_Providers_LinkedIn extends Hybrid_Provider_Model { /** * Provider API Wrapper - * @var LinkedIn + * @var LinkedIn */ public $api; @@ -28,6 +28,22 @@ class Hybrid_Providers_LinkedIn extends Hybrid_Provider_Model { if (!$this->config["keys"]["key"] || !$this->config["keys"]["secret"]) { throw new Exception("Your application key and secret are required in order to connect to {$this->providerId}.", 4); } + + if (empty($this->config['fields'])) { + $this->config['fields'] = array( + 'id', + 'first-name', + 'last-name', + 'public-profile-url', + 'picture-url', + 'email-address', + 'date-of-birth', + 'phone-numbers', + 'summary', + 'positions' + ); + } + if (!class_exists('OAuthConsumer', false)) { require_once Hybrid_Auth::$config["path_libraries"] . "OAuth/OAuth.php"; } @@ -97,7 +113,7 @@ class Hybrid_Providers_LinkedIn extends Hybrid_Provider_Model { function getUserProfile() { try { // http://developer.linkedin.com/docs/DOC-1061 - $response = $this->api->profile('~:(id,first-name,last-name,public-profile-url,picture-url,email-address,date-of-birth,phone-numbers,summary)'); + $response = $this->api->profile('~:('. implode(',', $this->config['fields']) .')'); } catch (LinkedInException $e) { throw new Exception("User profile request failed! {$this->providerId} returned an error: {$e->getMessage()}", 6, $e); } @@ -117,7 +133,22 @@ class Hybrid_Providers_LinkedIn extends Hybrid_Provider_Model { $this->user->profile->email = (string) $data->{'email-address'}; $this->user->profile->emailVerified = (string) $data->{'email-address'}; - $this->user->profile->photoURL = (string) $data->{'picture-url'}; + if ($data->{'positions'}) { + $this->user->profile->job_title = (string) $data->{'positions'}->{'position'}->{'title'}; + $this->user->profile->organization_name = (string) $data->{'positions'}->{'position'}->{'company'}->{'name'}; + } + + if (isset($data->{'picture-url'})) { + $this->user->profile->photoURL = (string) $data->{'picture-url'}; + + } elseif (isset($data->{'picture-urls'})) { + // picture-urls::(original) + $this->user->profile->photoURL = (string) $data->{'picture-urls'}->{'picture-url'}; + + } else { + $this->user->profile->photoURL = ""; + } + $this->user->profile->profileURL = (string) $data->{'public-profile-url'}; $this->user->profile->description = (string) $data->{'summary'}; diff --git a/e107_handlers/hybridauth/Hybrid/Providers/Twitter.php b/e107_handlers/hybridauth/Hybrid/Providers/Twitter.php index dad718f3f..6ea623134 100644 --- a/e107_handlers/hybridauth/Hybrid/Providers/Twitter.php +++ b/e107_handlers/hybridauth/Hybrid/Providers/Twitter.php @@ -131,7 +131,8 @@ class Hybrid_Providers_Twitter extends Hybrid_Provider_Model_OAuth1 { $this->user->profile->webSiteURL = (property_exists($response, 'url')) ? $response->url : ""; $this->user->profile->region = (property_exists($response, 'location')) ? $response->location : ""; if($includeEmail) $this->user->profile->email = (property_exists($response, 'email')) ? $response->email : ""; - + if($includeEmail) $this->user->profile->emailVerified = (property_exists($response, 'email')) ? $response->email : ""; + return $this->user->profile; } diff --git a/e107_handlers/hybridauth/Hybrid/User_Profile.php b/e107_handlers/hybridauth/Hybrid/User_Profile.php index 6a4070cd6..403be89c2 100644 --- a/e107_handlers/hybridauth/Hybrid/User_Profile.php +++ b/e107_handlers/hybridauth/Hybrid/User_Profile.php @@ -149,4 +149,15 @@ class Hybrid_User_Profile { */ public $zip = null; + /** + * Job title + * @var string + */ + public $job_title = null; + + /** + * Organization name + * @var string + */ + public $organization_name = null; } diff --git a/e107_handlers/hybridauth/Hybrid/thirdparty/Amazon/AmazonOauth2Client.php b/e107_handlers/hybridauth/Hybrid/thirdparty/Amazon/AmazonOauth2Client.php new file mode 100644 index 000000000..9c8a363a1 --- /dev/null +++ b/e107_handlers/hybridauth/Hybrid/thirdparty/Amazon/AmazonOauth2Client.php @@ -0,0 +1,125 @@ + $this->client_id, + "client_secret" => $this->client_secret, + "grant_type" => 'authorization_code', + "redirect_uri" => $this->redirect_uri, + "code" => $code, + ); + + $response = $this->request( $this->token_url, http_build_query($params), $this->curl_authenticate_method ); + + $response = $this->parseRequestResult( $response ); + + if ( ! $response || ! isset( $response->access_token ) ){ + throw new Exception( "The Authorization Service has return: " . $response->error ); + } + + if( isset( $response->access_token ) ) $this->access_token = $response->access_token; + if( isset( $response->refresh_token ) ) $this->refresh_token = $response->refresh_token; + if( isset( $response->expires_in ) ) $this->access_token_expires_in = $response->expires_in; + + // calculate when the access token expire + if( isset($response->expires_in)) { + $this->access_token_expires_at = time() + $response->expires_in; + } + + return $response; + } + + private function request( $url, $params=false, $type="GET" ) + { + Hybrid_Logger::info( "Enter OAuth2Client::request( $url )" ); + Hybrid_Logger::debug( "OAuth2Client::request(). dump request params: ", serialize( $params ) ); + + if( $type == "GET" ){ + $url = $url . ( strpos( $url, '?' ) ? '&' : '?' ) . http_build_query($params, '', '&'); + } + + $this->http_info = array(); + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL , $url ); + curl_setopt($ch, CURLOPT_RETURNTRANSFER , 1 ); + curl_setopt($ch, CURLOPT_TIMEOUT , $this->curl_time_out ); + curl_setopt($ch, CURLOPT_USERAGENT , $this->curl_useragent ); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT , $this->curl_connect_time_out ); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER , $this->curl_ssl_verifypeer ); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST , $this->curl_ssl_verifyhost ); + curl_setopt($ch, CURLOPT_HTTPHEADER , $this->curl_header ); + + if ($this->curl_compressed){ + curl_setopt($ch, CURLOPT_ENCODING, "gzip,deflate"); + } + + if($this->curl_proxy){ + curl_setopt( $ch, CURLOPT_PROXY , $this->curl_proxy); + } + + if( $type == "POST" ){ + curl_setopt($ch, CURLOPT_POST, 1); + if($params) curl_setopt( $ch, CURLOPT_POSTFIELDS, $params ); + } + if( $type == "DELETE" ){ + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE"); + } + if( $type == "PATCH" ){ + curl_setopt($ch, CURLOPT_POST, 1); + if($params) curl_setopt( $ch, CURLOPT_POSTFIELDS, $params ); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PATCH"); + } + $response = curl_exec($ch); + if( $response === false ) { + Hybrid_Logger::error( "OAuth2Client::request(). curl_exec error: ", curl_error($ch) ); + } + Hybrid_Logger::debug( "OAuth2Client::request(). dump request info: ", serialize( curl_getinfo($ch) ) ); + Hybrid_Logger::debug( "OAuth2Client::request(). dump request result: ", serialize( $response ) ); + + $this->http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $this->http_info = array_merge($this->http_info, curl_getinfo($ch)); + + curl_close ($ch); + + return $response; + } + + private function parseRequestResult( $result ) + { + if( json_decode( $result ) ) return json_decode( $result ); + + parse_str( $result, $output ); + + $result = new StdClass(); + + foreach( $output as $k => $v ) + $result->$k = $v; + + return $result; + } +} diff --git a/e107_handlers/hybridauth/Hybrid/thirdparty/OAuth/OAuth2Client.php b/e107_handlers/hybridauth/Hybrid/thirdparty/OAuth/OAuth2Client.php index ceb42f3ef..c9bc769d0 100644 --- a/e107_handlers/hybridauth/Hybrid/thirdparty/OAuth/OAuth2Client.php +++ b/e107_handlers/hybridauth/Hybrid/thirdparty/OAuth/OAuth2Client.php @@ -26,7 +26,6 @@ class OAuth2Client //-- public $sign_token_name = "access_token"; - public $decode_json = true; public $curl_time_out = 30; public $curl_connect_time_out = 30; public $curl_ssl_verifypeer = false; @@ -127,7 +126,7 @@ class OAuth2Client /** * Format and sign an oauth for provider api */ - public function api( $url, $method = "GET", $parameters = array() ) + public function api( $url, $method = "GET", $parameters = array(), $decode_json = true ) { if ( strrpos($url, 'http://') !== 0 && strrpos($url, 'https://') !== 0 ) { $url = $this->api_base_url . $url; @@ -139,9 +138,11 @@ class OAuth2Client switch( $method ){ case 'GET' : $response = $this->request( $url, $parameters, "GET" ); break; case 'POST' : $response = $this->request( $url, $parameters, "POST" ); break; + case 'DELETE' : $response = $this->request( $url, $parameters, "DELETE" ); break; + case 'PATCH' : $response = $this->request( $url, $parameters, "PATCH" ); break; } - if( $response && $this->decode_json ){ + if( $response && $decode_json ){ return $this->response = json_decode( $response ); } @@ -161,17 +162,17 @@ class OAuth2Client /** * GET wrapper for provider apis request */ - function get( $url, $parameters = array() ) + function get( $url, $parameters = array(), $decode_json = true ) { - return $this->api( $url, 'GET', $parameters ); + return $this->api( $url, 'GET', $parameters, $decode_json ); } /** * POST wrapper for provider apis request */ - function post( $url, $parameters = array() ) + function post( $url, $parameters = array(), $decode_json = true ) { - return $this->api( $url, 'POST', $parameters ); + return $this->api( $url, 'POST', $parameters, $decode_json ); } // -- tokens @@ -234,7 +235,14 @@ class OAuth2Client curl_setopt($ch, CURLOPT_POST, 1); if($params) curl_setopt( $ch, CURLOPT_POSTFIELDS, $params ); } - + if( $type == "DELETE" ){ + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE"); + } + if( $type == "PATCH" ){ + curl_setopt($ch, CURLOPT_POST, 1); + if($params) curl_setopt( $ch, CURLOPT_POSTFIELDS, $params ); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PATCH"); + } $response = curl_exec($ch); if( $response === false ) { Hybrid_Logger::error( "OAuth2Client::request(). curl_exec error: ", curl_error($ch) ); @@ -263,4 +271,18 @@ class OAuth2Client return $result; } + /** + * DELETE wrapper for provider apis request + */ + function delete( $url, $parameters = array() ) + { + return $this->api( $url, 'DELETE', $parameters ); + } + /** + * PATCH wrapper for provider apis request + */ + function patch( $url, $parameters = array() ) + { + return $this->api( $url, 'PATCH', $parameters ); + } } diff --git a/e107_handlers/hybridauth/index.php b/e107_handlers/hybridauth/index.php index a27a01913..10b79c150 100644 --- a/e107_handlers/hybridauth/index.php +++ b/e107_handlers/hybridauth/index.php @@ -2,7 +2,7 @@ /** * HybridAuth * http://hybridauth.sourceforge.net | http://github.com/hybridauth/hybridauth -* (c) 2009-2014, HybridAuth authors | http://hybridauth.sourceforge.net/licenses.html +* (c) 2009-2015, HybridAuth authors | http://hybridauth.sourceforge.net/licenses.html */ // ------------------------------------------------------------------------ @@ -10,6 +10,6 @@ // ------------------------------------------------------------------------ require_once("../../class2.php"); require_once( "Hybrid/Auth.php" ); -require_once( "Hybrid/Endpoint.php" ); +require_once( "Hybrid/Endpoint.php" ); Hybrid_Endpoint::process();