From 053aeba110edfb543b07fc58d462477b2b9c7de9 Mon Sep 17 00:00:00 2001 From: "spider@dev" Date: Fri, 30 Jun 2017 13:33:16 -0400 Subject: integrate HybridAuth signle sign on library --- hauth/Hybrid/Auth.php | 414 ++++++++ hauth/Hybrid/Endpoint.php | 222 +++++ hauth/Hybrid/Error.php | 88 ++ hauth/Hybrid/Exception.php | 17 + hauth/Hybrid/Logger.php | 102 ++ hauth/Hybrid/Provider_Adapter.php | 340 +++++++ hauth/Hybrid/Provider_Model.php | 247 +++++ hauth/Hybrid/Provider_Model_OAuth1.php | 174 ++++ hauth/Hybrid/Provider_Model_OAuth2.php | 184 ++++ hauth/Hybrid/Provider_Model_OpenID.php | 170 ++++ hauth/Hybrid/Providers/AOL.php | 18 + hauth/Hybrid/Providers/Amazon.php | 85 ++ hauth/Hybrid/Providers/Dropbox.php | 83 ++ hauth/Hybrid/Providers/Facebook.php | 393 ++++++++ hauth/Hybrid/Providers/Foursquare.php | 121 +++ hauth/Hybrid/Providers/Google.php | 306 ++++++ hauth/Hybrid/Providers/Instagram.php | 91 ++ hauth/Hybrid/Providers/LinkedIn.php | 170 ++++ hauth/Hybrid/Providers/Live.php | 108 ++ hauth/Hybrid/Providers/OpenID.php | 16 + hauth/Hybrid/Providers/Paypal.php | 146 +++ hauth/Hybrid/Providers/Twitter.php | 264 +++++ hauth/Hybrid/Providers/Yahoo.php | 269 +++++ hauth/Hybrid/Storage.php | 141 +++ hauth/Hybrid/StorageInterface.php | 29 + hauth/Hybrid/User.php | 40 + hauth/Hybrid/User_Activity.php | 55 + hauth/Hybrid/User_Contact.php | 60 ++ hauth/Hybrid/User_Profile.php | 163 +++ hauth/Hybrid/index.html | 10 + hauth/Hybrid/resources/index.html | 10 + hauth/Hybrid/resources/openid_policy.html | 10 + hauth/Hybrid/resources/openid_realm.html | 13 + hauth/Hybrid/resources/openid_xrds.xml | 12 + .../thirdparty/Amazon/AmazonOAuth2Client.php | 125 +++ hauth/Hybrid/thirdparty/OAuth/OAuth.php | 901 +++++++++++++++++ hauth/Hybrid/thirdparty/OAuth/OAuth1Client.php | 264 +++++ hauth/Hybrid/thirdparty/OAuth/OAuth2Client.php | 302 ++++++ hauth/Hybrid/thirdparty/OpenID/LightOpenID.php | 1051 ++++++++++++++++++++ .../thirdparty/Paypal/PaypalOAuth2Client.php | 142 +++ hauth/Hybrid/thirdparty/index.html | 10 + hauth/connect.php | 72 ++ hauth/disconnect.php | 23 + hauth/index.php | 17 + hauth/live.php | 13 + 45 files changed, 7491 insertions(+) create mode 100644 hauth/Hybrid/Auth.php create mode 100644 hauth/Hybrid/Endpoint.php create mode 100644 hauth/Hybrid/Error.php create mode 100644 hauth/Hybrid/Exception.php create mode 100644 hauth/Hybrid/Logger.php create mode 100644 hauth/Hybrid/Provider_Adapter.php create mode 100644 hauth/Hybrid/Provider_Model.php create mode 100644 hauth/Hybrid/Provider_Model_OAuth1.php create mode 100644 hauth/Hybrid/Provider_Model_OAuth2.php create mode 100644 hauth/Hybrid/Provider_Model_OpenID.php create mode 100644 hauth/Hybrid/Providers/AOL.php create mode 100644 hauth/Hybrid/Providers/Amazon.php create mode 100644 hauth/Hybrid/Providers/Dropbox.php create mode 100644 hauth/Hybrid/Providers/Facebook.php create mode 100644 hauth/Hybrid/Providers/Foursquare.php create mode 100644 hauth/Hybrid/Providers/Google.php create mode 100644 hauth/Hybrid/Providers/Instagram.php create mode 100644 hauth/Hybrid/Providers/LinkedIn.php create mode 100644 hauth/Hybrid/Providers/Live.php create mode 100644 hauth/Hybrid/Providers/OpenID.php create mode 100644 hauth/Hybrid/Providers/Paypal.php create mode 100644 hauth/Hybrid/Providers/Twitter.php create mode 100644 hauth/Hybrid/Providers/Yahoo.php create mode 100644 hauth/Hybrid/Storage.php create mode 100644 hauth/Hybrid/StorageInterface.php create mode 100644 hauth/Hybrid/User.php create mode 100644 hauth/Hybrid/User_Activity.php create mode 100644 hauth/Hybrid/User_Contact.php create mode 100644 hauth/Hybrid/User_Profile.php create mode 100644 hauth/Hybrid/index.html create mode 100644 hauth/Hybrid/resources/index.html create mode 100644 hauth/Hybrid/resources/openid_policy.html create mode 100644 hauth/Hybrid/resources/openid_realm.html create mode 100644 hauth/Hybrid/resources/openid_xrds.xml create mode 100644 hauth/Hybrid/thirdparty/Amazon/AmazonOAuth2Client.php create mode 100644 hauth/Hybrid/thirdparty/OAuth/OAuth.php create mode 100644 hauth/Hybrid/thirdparty/OAuth/OAuth1Client.php create mode 100644 hauth/Hybrid/thirdparty/OAuth/OAuth2Client.php create mode 100644 hauth/Hybrid/thirdparty/OpenID/LightOpenID.php create mode 100644 hauth/Hybrid/thirdparty/Paypal/PaypalOAuth2Client.php create mode 100644 hauth/Hybrid/thirdparty/index.html create mode 100644 hauth/connect.php create mode 100644 hauth/disconnect.php create mode 100644 hauth/index.php create mode 100644 hauth/live.php (limited to 'hauth') diff --git a/hauth/Hybrid/Auth.php b/hauth/Hybrid/Auth.php new file mode 100644 index 0000000..1f9f1e6 --- /dev/null +++ b/hauth/Hybrid/Auth.php @@ -0,0 +1,414 @@ +getSessionData()); + Hybrid_Logger::info("Hybrid_Auth initialize: check if any error is stored on the endpoint..."); + + if (Hybrid_Error::hasError()) { + $m = Hybrid_Error::getErrorMessage(); + $c = Hybrid_Error::getErrorCode(); + $p = Hybrid_Error::getErrorPrevious(); + + Hybrid_Logger::error("Hybrid_Auth initialize: A stored Error found, Throw an new Exception and delete it from the store: Error#$c, '$m'"); + + Hybrid_Error::clearError(); + + // try to provide the previous if any + // Exception::getPrevious (PHP 5 >= 5.3.0) http://php.net/manual/en/exception.getprevious.php + if (version_compare(PHP_VERSION, '5.3.0', '>=') && ($p instanceof Exception)) { + throw new Exception($m, $c, $p); + } else { + throw new Exception($m, $c); + } + } + + Hybrid_Logger::info("Hybrid_Auth initialize: no error found. initialization succeed."); + } + + /** + * Hybrid storage system accessor + * + * Users sessions are stored using HybridAuth storage system ( HybridAuth 2.0 handle PHP Session only) and can be accessed directly by + * Hybrid_Auth::storage()->get($key) to retrieves the data for the given key, or calling + * Hybrid_Auth::storage()->set($key, $value) to store the key => $value set. + * + * @return Hybrid_Storage + */ + public static function storage() { + return Hybrid_Auth::$store; + } + + /** + * Get hybridauth session data + * @return string|null + */ + function getSessionData() { + return Hybrid_Auth::storage()->getSessionData(); + } + + /** + * Restore hybridauth session data + * + * @param string $sessiondata Serialized session data + * @retun void + */ + function restoreSessionData($sessiondata = null) { + Hybrid_Auth::storage()->restoreSessionData($sessiondata); + } + + /** + * Try to authenticate the user with a given provider. + * + * If the user is already connected we just return and instance of provider adapter, + * ELSE, try to authenticate and authorize the user with the provider. + * + * $params is generally an array with required info in order for this provider and HybridAuth to work, + * like : + * hauth_return_to: URL to call back after authentication is done + * openid_identifier: The OpenID identity provider identifier + * google_service: can be "Users" for Google user accounts service or "Apps" for Google hosted Apps + * + * @param string $providerId ID of the provider + * @param array $params Params + * @return + */ + public static function authenticate($providerId, $params = null) { + Hybrid_Logger::info("Enter Hybrid_Auth::authenticate( $providerId )"); + + if (!Hybrid_Auth::storage()->get("hauth_session.$providerId.is_logged_in")) { + // if user not connected to $providerId then try setup a new adapter and start the login process for this provider + Hybrid_Logger::info("Hybrid_Auth::authenticate( $providerId ), User not connected to the provider. Try to authenticate.."); + $provider_adapter = Hybrid_Auth::setup($providerId, $params); + $provider_adapter->login(); + } else { + // else, then return the adapter instance for the given provider + Hybrid_Logger::info("Hybrid_Auth::authenticate( $providerId ), User is already connected to this provider. Return the adapter instance."); + return Hybrid_Auth::getAdapter($providerId); + } + } + + /** + * Return the adapter instance for an authenticated provider + * + * @param string $providerId ID of the provider + * @return Hybrid_Provider_Adapter + */ + public static function getAdapter($providerId = null) { + Hybrid_Logger::info("Enter Hybrid_Auth::getAdapter( $providerId )"); + return Hybrid_Auth::setup($providerId); + } + + /** + * Setup an adapter for a given provider + * + * @param string $providerId ID of the provider + * @param array $params Adapter params + * @return Hybrid_Provider_Adapter + */ + public static function setup($providerId, $params = null) { + Hybrid_Logger::debug("Enter Hybrid_Auth::setup( $providerId )", $params); + + if (!$params) { + $params = Hybrid_Auth::storage()->get("hauth_session.$providerId.id_provider_params"); + + Hybrid_Logger::debug("Hybrid_Auth::setup( $providerId ), no params given. Trying to get the stored for this provider.", $params); + } + + if (!$params) { + $params = array(); + Hybrid_Logger::info("Hybrid_Auth::setup( $providerId ), no stored params found for this provider. Initialize a new one for new session"); + } + + if (is_array($params) && !isset($params["hauth_return_to"])) { + $params["hauth_return_to"] = Hybrid_Auth::getCurrentUrl(); + Hybrid_Logger::debug("Hybrid_Auth::setup( $providerId ). HybridAuth Callback URL set to: ", $params["hauth_return_to"]); + } + + # instantiate a new IDProvider Adapter + $provider = new Hybrid_Provider_Adapter(); + $provider->factory($providerId, $params); + return $provider; + } + + /** + * Check if the current user is connected to a given provider + * + * @param string $providerId ID of the provider + * @return bool + */ + public static function isConnectedWith($providerId) { + return (bool) Hybrid_Auth::storage()->get("hauth_session.{$providerId}.is_logged_in"); + } + + /** + * Return array listing all authenticated providers + * @return array + */ + public static function getConnectedProviders() { + $idps = array(); + + foreach (Hybrid_Auth::$config["providers"] as $idpid => $params) { + if (Hybrid_Auth::isConnectedWith($idpid)) { + $idps[] = $idpid; + } + } + + return $idps; + } + + /** + * Return array listing all enabled providers as well as a flag if you are connected + * + * + * array( + * 'Facebook' => array( + * 'connected' => true + * ) + * ) + * + * @return array + */ + public static function getProviders() { + $idps = array(); + + foreach (Hybrid_Auth::$config["providers"] as $idpid => $params) { + if ($params['enabled']) { + $idps[$idpid] = array('connected' => false); + + if (Hybrid_Auth::isConnectedWith($idpid)) { + $idps[$idpid]['connected'] = true; + } + } + } + + return $idps; + } + + /** + * A generic function to logout all connected provider at once + * @return void + */ + public static function logoutAllProviders() { + $idps = Hybrid_Auth::getConnectedProviders(); + + foreach ($idps as $idp) { + $adapter = Hybrid_Auth::getAdapter($idp); + $adapter->logout(); + } + } + + /** + * Utility function, redirect to a given URL with php header or using javascript location.href + * + * @param string $url URL to redirect to + * @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 + if ((PHP_VERSION_ID >= 50400 && PHP_SESSION_ACTIVE === session_status()) || (PHP_VERSION_ID < 50400 && isset($_SESSION) && session_id())) { + session_write_close(); + } + + if ($mode == "PHP") { + header("Location: $url"); + } elseif ($mode == "JS") { + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo 'Redirecting, please wait...'; + echo ''; + echo ''; + } + + die(); + } + + /** + * Utility function, return the current url + * + * @param bool $request_uri true to get $_SERVER['REQUEST_URI'], false for $_SERVER['PHP_SELF'] + * @return string + */ + public static function getCurrentUrl($request_uri = true) { + if (php_sapi_name() == 'cli') { + return ''; + } + + $protocol = '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://'; + } + + $url = $protocol . $_SERVER['HTTP_HOST']; + + if ($request_uri) { + $url .= $_SERVER['REQUEST_URI']; + } else { + $url .= $_SERVER['PHP_SELF']; + } + + // return current url + return $url; + } + +} diff --git a/hauth/Hybrid/Endpoint.php b/hauth/Hybrid/Endpoint.php new file mode 100644 index 0000000..7813fee --- /dev/null +++ b/hauth/Hybrid/Endpoint.php @@ -0,0 +1,222 @@ +here we need to parse $_SERVER[QUERY_STRING] + $request = $_REQUEST; + if (isset($_SERVER["QUERY_STRING"]) && strrpos($_SERVER["QUERY_STRING"], '?')) { + $_SERVER["QUERY_STRING"] = str_replace("?", "&", $_SERVER["QUERY_STRING"]); + parse_str($_SERVER["QUERY_STRING"], $request); + } + } + + // Setup request variable + $this->request = $request; + + // If openid_policy requested, we return our policy document + if (isset($this->request["get"]) && $this->request["get"] == "openid_policy") { + $this->processOpenidPolicy(); + } + + // If openid_xrds requested, we return our XRDS document + if (isset($this->request["get"]) && $this->request["get"] == "openid_xrds") { + $this->processOpenidXRDS(); + } + + // If we get a hauth.start + if (isset($this->request["hauth_start"]) && $this->request["hauth_start"]) { + $this->processAuthStart(); + } + // Else if hauth.done + elseif (isset($this->request["hauth_done"]) && $this->request["hauth_done"]) { + $this->processAuthDone(); + } + // Else we advertise our XRDS document, something supposed to be done from the Realm URL page + else { + $this->processOpenidRealm(); + } + } + + /** + * Process the current request + * + * @param array $request The current request parameters. Leave as null to default to use $_REQUEST. + * @return Hybrid_Endpoint + */ + public static function process($request = null) { + // Trick for PHP 5.2, because it doesn't support late static binding + $class = function_exists('get_called_class') ? get_called_class() : __CLASS__; + new $class($request); + } + + /** + * Process OpenID policy request + * @return void + */ + protected function processOpenidPolicy() { + $output = file_get_contents(dirname(__FILE__) . "/resources/openid_policy.html"); + print $output; + die(); + } + + /** + * Process OpenID XRDS request + * @return void + */ + protected function processOpenidXRDS() { + header("Content-Type: application/xrds+xml"); + + $output = str_replace("{RETURN_TO_URL}", str_replace( + array("<", ">", "\"", "'", "&"), array("<", ">", """, "'", "&"), Hybrid_Auth::getCurrentUrl(false) + ), file_get_contents(dirname(__FILE__) . "/resources/openid_xrds.xml")); + print $output; + die(); + } + + /** + * Process OpenID realm request + * @return void + */ + protected function processOpenidRealm() { + $output = str_replace("{X_XRDS_LOCATION}", htmlentities(Hybrid_Auth::getCurrentUrl(false), ENT_QUOTES, 'UTF-8') + . "?get=openid_xrds&v=" + . Hybrid_Auth::$version, file_get_contents(dirname(__FILE__) . "/resources/openid_realm.html")); + print $output; + die(); + } + + /** + * Define: endpoint step 3 + * @return void + * @throws Hybrid_Exception + */ + protected function processAuthStart() { + $this->authInit(); + + $provider_id = trim(strip_tags($this->request["hauth_start"])); + + // check if page accessed directly + if (!Hybrid_Auth::storage()->get("hauth_session.$provider_id.hauth_endpoint")) { + Hybrid_Logger::error("Endpoint: hauth_endpoint parameter is not defined on hauth_start, halt login process!"); + + throw new Hybrid_Exception("You cannot access this page directly."); + } + + // define:hybrid.endpoint.php step 2. + $hauth = Hybrid_Auth::setup($provider_id); + + // if REQUESTed hauth_idprovider is wrong, session not created, etc. + if (!$hauth) { + Hybrid_Logger::error("Endpoint: Invalid parameter on hauth_start!"); + throw new Hybrid_Exception("Invalid parameter! Please return to the login page and try again."); + } + + try { + Hybrid_Logger::info("Endpoint: call adapter [{$provider_id}] loginBegin()"); + + $hauth->adapter->loginBegin(); + } catch (Exception $e) { + Hybrid_Logger::error("Exception:" . $e->getMessage(), $e); + Hybrid_Error::setError($e->getMessage(), $e->getCode(), $e->getTraceAsString(), $e->getPrevious()); + + $hauth->returnToCallbackUrl(); + } + + die(); + } + + /** + * Define: endpoint step 3.1 and 3.2 + * @return void + * @throws Hybrid_Exception + */ + protected function processAuthDone() { + $this->authInit(); + + $provider_id = trim(strip_tags($this->request["hauth_done"])); + + $hauth = Hybrid_Auth::setup($provider_id); + + if (!$hauth) { + Hybrid_Logger::error("Endpoint: Invalid parameter on hauth_done!"); + + $hauth->adapter->setUserUnconnected(); + + throw new Hybrid_Exception("Invalid parameter! Please return to the login page and try again."); + } + + try { + Hybrid_Logger::info("Endpoint: call adapter [{$provider_id}] loginFinish() "); + $hauth->adapter->loginFinish(); + } catch (Exception $e) { + Hybrid_Logger::error("Exception:" . $e->getMessage(), $e); + Hybrid_Error::setError($e->getMessage(), $e->getCode(), $e->getTraceAsString(), $e->getPrevious()); + + $hauth->adapter->setUserUnconnected(); + } + + Hybrid_Logger::info("Endpoint: job done. return to callback url."); + + $hauth->returnToCallbackUrl(); + die(); + } + + /** + * Initializes authentication + * @throws Hybrid_Exception + */ + protected function authInit() { + if (!$this->initDone) { + $this->initDone = true; + + // Init Hybrid_Auth + try { + if (!class_exists("Hybrid_Storage", false)) { + require_once realpath(dirname(__FILE__)) . "/Storage.php"; + } + if (!class_exists("Hybrid_Exception", false)) { + require_once realpath(dirname(__FILE__)) . "/Exception.php"; + } + if (!class_exists("Hybrid_Logger", false)) { + require_once realpath(dirname(__FILE__)) . "/Logger.php"; + } + + $storage = new Hybrid_Storage(); + + // Check if Hybrid_Auth session already exist + if (!$storage->config("CONFIG")) { + throw new Hybrid_Exception("You cannot access this page directly."); + } + + Hybrid_Auth::initialize($storage->config("CONFIG")); + } catch (Exception $e) { + Hybrid_Logger::error("Endpoint: Error while trying to init Hybrid_Auth: " . $e->getMessage()); + throw new Hybrid_Exception( "Endpoint: Error while trying to init Hybrid_Auth: " . $e->getMessage(), $e->getCode(), $e ); + } + } + } + +} diff --git a/hauth/Hybrid/Error.php b/hauth/Hybrid/Error.php new file mode 100644 index 0000000..7013b49 --- /dev/null +++ b/hauth/Hybrid/Error.php @@ -0,0 +1,88 @@ +set("hauth_session.error.status", 1); + Hybrid_Auth::storage()->set("hauth_session.error.message", $message); + Hybrid_Auth::storage()->set("hauth_session.error.code", $code); + Hybrid_Auth::storage()->set("hauth_session.error.trace", $trace); + Hybrid_Auth::storage()->set("hauth_session.error.previous", $previous); + } + + /** + * Clear the last error + * @return void + */ + public static function clearError() { + Hybrid_Logger::info("Enter Hybrid_Error::clearError()"); + + Hybrid_Auth::storage()->delete("hauth_session.error.status"); + Hybrid_Auth::storage()->delete("hauth_session.error.message"); + Hybrid_Auth::storage()->delete("hauth_session.error.code"); + Hybrid_Auth::storage()->delete("hauth_session.error.trace"); + Hybrid_Auth::storage()->delete("hauth_session.error.previous"); + } + + /** + * Checks to see if there is a an error. + * @return boolean true if there is an error. + */ + public static function hasError() { + return (bool) Hybrid_Auth::storage()->get("hauth_session.error.status"); + } + + /** + * Return error message + * @return string + */ + public static function getErrorMessage() { + return Hybrid_Auth::storage()->get("hauth_session.error.message"); + } + + /** + * Return error code + * @return int + */ + public static function getErrorCode() { + return Hybrid_Auth::storage()->get("hauth_session.error.code"); + } + + /** + * Return string detailed error backtrace as string + * @return string + */ + public static function getErrorTrace() { + return Hybrid_Auth::storage()->get("hauth_session.error.trace"); + } + + /** + * Detailed error backtrace as string + * @return string + */ + public static function getErrorPrevious() { + return Hybrid_Auth::storage()->get("hauth_session.error.previous"); + } + +} diff --git a/hauth/Hybrid/Exception.php b/hauth/Hybrid/Exception.php new file mode 100644 index 0000000..8c8c2d1 --- /dev/null +++ b/hauth/Hybrid/Exception.php @@ -0,0 +1,17 @@ +format(DATE_ATOM), + $message, + print_r($object, true) . PHP_EOL, + )), FILE_APPEND + ); + } + } + + /** + * Logs an info message + * + * @param string $message Info message + * @return void + */ + public static function info($message) { + if (in_array(Hybrid_Auth::$config["debug_mode"], array(true, 'info'), true)) { + $dt = new DateTime('now', new DateTimeZone( 'UTC' )); + file_put_contents(Hybrid_Auth::$config["debug_file"], implode(' -- ', array( + "INFO", + $_SERVER['REMOTE_ADDR'], + $dt->format(DATE_ATOM), + $message . PHP_EOL, + )), FILE_APPEND); + } + } + + /** + * Logs an error message with an object dump + * + * @param string $message Error message + * @param stdClass $object Object being debugged + * @return void + */ + public static function error($message, $object = null) { + if (isset(Hybrid_Auth::$config["debug_mode"]) && in_array(Hybrid_Auth::$config["debug_mode"], array(true, 'info', 'error'), true)) { + $dt = new DateTime('now', new DateTimeZone( 'UTC' )); + file_put_contents(Hybrid_Auth::$config["debug_file"], implode(' -- ', array( + 'ERROR', + $_SERVER['REMOTE_ADDR'], + $dt->format(DATE_ATOM), + $message, + print_r($object, true) . PHP_EOL + )), FILE_APPEND); + } + } + + /** + * Dumps the data in the way suitable to be output in log files for debug purposes + * + * @param mixed $data + * + * @return string + */ + public static function dumpData($data) { + return var_export($data, true); + } + +} diff --git a/hauth/Hybrid/Provider_Adapter.php b/hauth/Hybrid/Provider_Adapter.php new file mode 100644 index 0000000..f96a500 --- /dev/null +++ b/hauth/Hybrid/Provider_Adapter.php @@ -0,0 +1,340 @@ +id = $id; + $this->params = $params; + $this->id = $this->getProviderCiId($this->id); + $this->config = $this->getConfigById($this->id); + + # check the IDp id + if (!$this->id) { + throw new Exception("No provider ID specified.", 2); + } + + # check the IDp config + if (!$this->config) { + throw new Exception("Unknown Provider ID, check your configuration file.", 3); + } + + # check the IDp adapter is enabled + if (!$this->config["enabled"]) { + throw new Exception("The provider '{$this->id}' is not enabled.", 3); + } + + # include the adapter wrapper + if (isset($this->config["wrapper"]) && is_array($this->config["wrapper"])) { + if (isset($this->config["wrapper"]["path"])) { + require_once $this->config["wrapper"]["path"]; + } + + if (!class_exists($this->config["wrapper"]["class"])) { + throw new Exception("Unable to load the adapter class.", 3); + } + + $this->wrapper = $this->config["wrapper"]["class"]; + } else { + require_once Hybrid_Auth::$config["path_providers"] . $this->id . ".php"; + + $this->wrapper = "Hybrid_Providers_" . $this->id; + } + + # create the adapter instance, and pass the current params and config + $this->adapter = new $this->wrapper($this->id, $this->config, $this->params); + + return $this; + } + + /** + * Hybrid_Provider_Adapter::login(), prepare the user session and the authentication request + * for index.php + * @return void + * @throw Exception + */ + function login() { + Hybrid_Logger::info("Enter Hybrid_Provider_Adapter::login( {$this->id} ) "); + + if (!$this->adapter) { + throw new Exception("Hybrid_Provider_Adapter::login() should not directly used."); + } + + // clear all unneeded params + foreach (Hybrid_Auth::$config["providers"] as $idpid => $params) { + Hybrid_Auth::storage()->delete("hauth_session.{$idpid}.hauth_return_to"); + Hybrid_Auth::storage()->delete("hauth_session.{$idpid}.hauth_endpoint"); + Hybrid_Auth::storage()->delete("hauth_session.{$idpid}.id_provider_params"); + } + + // make a fresh start + $this->logout(); + + # get hybridauth base url + if (empty(Hybrid_Auth::$config["base_url"])) { + // the base url wasn't provide, so we must use the current + // url (which makes sense actually) + $url = empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] == 'off' ? 'http' : 'https'; + $url .= '://' . $_SERVER['HTTP_HOST']; + $url .= $_SERVER['REQUEST_URI']; + $HYBRID_AUTH_URL_BASE = $url; + } else { + $HYBRID_AUTH_URL_BASE = Hybrid_Auth::$config["base_url"]; + } + + // make sure params is array + if (!is_array($this->params)) { + $this->params = array(); + } + + # we make use of session_id() as storage hash to identify the current user + # using session_regenerate_id() will be a problem, but .. + $this->params["hauth_token"] = session_id(); + + # set request timestamp + $this->params["hauth_time"] = time(); + + # for default HybridAuth endpoint url hauth_login_start_url + # auth.start required the IDp ID + # auth.time optional login request timestamp + 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 + 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"]); + } + if (isset($this->params["login_done"])) { + Hybrid_Auth::storage()->set("hauth_session.{$this->id}.hauth_endpoint", $this->params["login_done"]); + } + Hybrid_Auth::storage()->set("hauth_session.{$this->id}.id_provider_params", $this->params); + + // store config to be used by the end point + Hybrid_Auth::storage()->config("CONFIG", Hybrid_Auth::$config); + + // move on + Hybrid_Logger::debug("Hybrid_Provider_Adapter::login( {$this->id} ), redirect the user to login_start URL."); + + // 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"]); + } + } + + /** + * Let hybridauth forget all about the user for the current provider + * @return bool + */ + function logout() { + $this->adapter->logout(); + } + + // -------------------------------------------------------------------- + + /** + * Return true if the user is connected to the current provider + * @return bool + */ + public function isUserConnected() { + return $this->adapter->isUserConnected(); + } + + // -------------------------------------------------------------------- + + /** + * Call adapter methods defined in the adapter model: + * getUserProfile() + * getUserContacts() + * getUserActivity() + * setUserStatus() + * + * @param string $name Method name + * @param array $arguments Call arguments + * @return mixed + * @throws Exception + */ + public function __call($name, $arguments) { + Hybrid_Logger::info("Enter Hybrid_Provider_Adapter::$name(), Provider: {$this->id}"); + + if (!$this->isUserConnected()) { + throw new Exception("User not connected to the provider {$this->id}.", 7); + } + + if (!method_exists($this->adapter, $name)) { + throw new Exception("Call to undefined function Hybrid_Providers_{$this->id}::$name()."); + } + + return call_user_func_array(array($this->adapter, $name), $arguments); + } + + /** + * If the user is connected, then return the access_token and access_token_secret + * if the provider api use oauth + * + * + * array( + * 'access_token' => '', + * 'access_token_secret' => '', + * 'refresh_token' => '', + * 'expires_in' => '', + * 'expires_at' => '', + * ) + * + * @return array + */ + public function getAccessToken() { + if (!$this->adapter->isUserConnected()) { + Hybrid_Logger::error("User not connected to the provider."); + throw new Exception("User not connected to the provider.", 7); + } + + return array( + "access_token" => $this->adapter->token("access_token"), // OAuth access token + "access_token_secret" => $this->adapter->token("access_token_secret"), // OAuth access token secret + "refresh_token" => $this->adapter->token("refresh_token"), // OAuth refresh token + "expires_in" => $this->adapter->token("expires_in"), // OPTIONAL. The duration in seconds of the access token lifetime + "expires_at" => $this->adapter->token("expires_at"), // OPTIONAL. Timestamp when the access_token expire. if not provided by the social api, then it should be calculated: expires_at = now + expires_in + ); + } + + /** + * Naive getter of the current connected IDp API client + * @return stdClass + * @throws Exception + */ + function api() { + if (!$this->adapter->isUserConnected()) { + Hybrid_Logger::error("User not connected to the provider."); + + throw new Exception("User not connected to the provider.", 7); + } + return $this->adapter->api; + } + + /** + * Redirect the user to hauth_return_to (the callback url) + * @return void + */ + function returnToCallbackUrl() { + // 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"); + Hybrid_Auth::storage()->delete("hauth_session.{$this->id}.id_provider_params"); + + // back to home + Hybrid_Auth::redirect($callback_url); + } + + /** + * Return the provider config by id + * + * @param string $id Config key + * @return mixed + */ + function getConfigById($id) { + if (isset(Hybrid_Auth::$config["providers"][$id])) { + return Hybrid_Auth::$config["providers"][$id]; + } + return null; + } + + /** + * Return the provider config by id; case insensitive + * + * @param string $id Provider id + * @return mixed + */ + function getProviderCiId($id) { + foreach (Hybrid_Auth::$config["providers"] as $idpid => $params) { + if (strtolower($idpid) == strtolower($id)) { + return $idpid; + } + } + return null; + } + +} diff --git a/hauth/Hybrid/Provider_Model.php b/hauth/Hybrid/Provider_Model.php new file mode 100644 index 0000000..9f5798b --- /dev/null +++ b/hauth/Hybrid/Provider_Model.php @@ -0,0 +1,247 @@ +params = Hybrid_Auth::storage()->get("hauth_session.$providerId.id_provider_params"); + } else { + $this->params = $params; + } + + // idp id + $this->providerId = $providerId; + + // set HybridAuth endpoint for this provider + $this->endpoint = Hybrid_Auth::storage()->get("hauth_session.$providerId.hauth_endpoint"); + + // idp config + $this->config = $config; + + // new user instance + $this->user = new Hybrid_User(); + $this->user->providerId = $providerId; + + // initialize the current provider adapter + $this->initialize(); + + Hybrid_Logger::debug("Hybrid_Provider_Model::__construct( $providerId ) initialized. dump current adapter instance: ", serialize($this)); + } + + /** + * IDp wrappers initializer + * + * The main job of wrappers initializer is to performs (depend on the IDp api client it self): + * - include some libs needed by this provider, + * - check IDp key and secret, + * - set some needed parameters (stored in $this->params) by this IDp api client + * - create and setup an instance of the IDp api client on $this->api + * + * @return void + * @throws Exception + */ + abstract protected function initialize(); + + /** + * Begin login + * + * @return void + * @throws Exception + */ + abstract public function loginBegin(); + + /** + * Finish login + * @return void + * @throws Exception + */ + abstract public function loginFinish(); + + /** + * Generic logout, just erase current provider adapter stored data to let Hybrid_Auth all forget about it + * @return bool + */ + function logout() { + Hybrid_Logger::info("Enter [{$this->providerId}]::logout()"); + $this->clearTokens(); + return true; + } + + /** + * Grab the user profile from the IDp api client + * @return Hybrid_User_Profile + * @throws Exception + */ + function getUserProfile() { + Hybrid_Logger::error("HybridAuth do not provide users contacts list for {$this->providerId} yet."); + throw new Exception("Provider does not support this feature.", 8); + } + + /** + * Load the current logged in user contacts list from the IDp api client + * @return Hybrid_User_Contact[] + * @throws Exception + */ + function getUserContacts() { + Hybrid_Logger::error("HybridAuth do not provide users contacts list for {$this->providerId} yet."); + throw new Exception("Provider does not support this feature.", 8); + } + + /** + * Return the user activity stream + * @return Hybrid_User_Activity[] + * @throws Exception + */ + function getUserActivity($stream) { + Hybrid_Logger::error("HybridAuth do not provide user's activity stream for {$this->providerId} yet."); + throw new Exception("Provider does not support this feature.", 8); + } + + /** + * Set user status + * @return mixed Provider response + * @throws Exception + */ + function setUserStatus($status) { + Hybrid_Logger::error("HybridAuth do not provide user's activity stream for {$this->providerId} yet."); + throw new Exception("Provider does not support this feature.", 8); + } + + /** + * Return the user status + * @return mixed Provider response + * @throws Exception + */ + function getUserStatus($statusid) { + Hybrid_Logger::error("HybridAuth do not provide user's status for {$this->providerId} yet."); + throw new Exception("Provider does not support this feature.", 8); + } + + /** + * Return true if the user is connected to the current provider + * @return bool + */ + public function isUserConnected() { + return (bool) Hybrid_Auth::storage()->get("hauth_session.{$this->providerId}.is_logged_in"); + } + + /** + * Set user to connected + * @return void + */ + public function setUserConnected() { + Hybrid_Logger::info("Enter [{$this->providerId}]::setUserConnected()"); + Hybrid_Auth::storage()->set("hauth_session.{$this->providerId}.is_logged_in", 1); + } + + /** + * Set user to unconnected + * @return void + */ + public function setUserUnconnected() { + Hybrid_Logger::info("Enter [{$this->providerId}]::setUserUnconnected()"); + Hybrid_Auth::storage()->set("hauth_session.{$this->providerId}.is_logged_in", 0); + } + + /** + * Get or set a token + * @return string + */ + public function token($token, $value = null) { + if ($value === null) { + return Hybrid_Auth::storage()->get("hauth_session.{$this->providerId}.token.$token"); + } else { + Hybrid_Auth::storage()->set("hauth_session.{$this->providerId}.token.$token", $value); + } + } + + /** + * Delete a stored token + * @return void + */ + public function deleteToken($token) { + Hybrid_Auth::storage()->delete("hauth_session.{$this->providerId}.token.$token"); + } + + /** + * Clear all existent tokens for this provider + * @return void + */ + public function clearTokens() { + Hybrid_Auth::storage()->deleteMatch("hauth_session.{$this->providerId}."); + } + +} diff --git a/hauth/Hybrid/Provider_Model_OAuth1.php b/hauth/Hybrid/Provider_Model_OAuth1.php new file mode 100644 index 0000000..23fd2d3 --- /dev/null +++ b/hauth/Hybrid/Provider_Model_OAuth1.php @@ -0,0 +1,174 @@ + "OK: Success!", + 304 => "Not Modified: There was no new data to return.", + 400 => "Bad Request: The request was invalid.", + 401 => "Unauthorized.", + 403 => "Forbidden: The request is understood, but it has been refused.", + 404 => "Not Found: The URI requested is invalid or the resource requested does not exists.", + 406 => "Not Acceptable.", + 500 => "Internal Server Error: Something is broken.", + 502 => "Bad Gateway.", + 503 => "Service Unavailable." + ); + + if (!$code && $this->api) { + $code = $this->api->http_code; + } + + if (isset($http_status_codes[$code])) { + return $code . " " . $http_status_codes[$code]; + } + } + + /** + * {@inheritdoc} + */ + function initialize() { + // 1 - check application credentials + 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); + } + + // 2 - include OAuth lib and client + if (! class_exists('OAuthConsumer') ) { + require_once Hybrid_Auth::$config["path_libraries"] . "OAuth/OAuth.php"; + } + require_once Hybrid_Auth::$config["path_libraries"] . "OAuth/OAuth1Client.php"; + + // 3.1 - setup access_token if any stored + if ($this->token("access_token")) { + $this->api = new OAuth1Client( + $this->config["keys"]["key"], $this->config["keys"]["secret"], $this->token("access_token"), $this->token("access_token_secret") + ); + } + + // 3.2 - setup request_token if any stored, in order to exchange with an access token + elseif ($this->token("request_token")) { + $this->api = new OAuth1Client( + $this->config["keys"]["key"], $this->config["keys"]["secret"], $this->token("request_token"), $this->token("request_token_secret") + ); + } + + // 3.3 - instanciate OAuth client with client credentials + else { + $this->api = new OAuth1Client($this->config["keys"]["key"], $this->config["keys"]["secret"]); + } + + // Set curl proxy if exist + if (isset(Hybrid_Auth::$config["proxy"])) { + $this->api->curl_proxy = Hybrid_Auth::$config["proxy"]; + } + } + + /** + * {@inheritdoc} + */ + function loginBegin() { + $tokens = $this->api->requestToken($this->endpoint); + + // request tokens as received from provider + $this->request_tokens_raw = $tokens; + + // check the last HTTP status code returned + if ($this->api->http_code != 200) { + throw new Exception("Authentication failed! {$this->providerId} returned an error. " . $this->errorMessageByStatus($this->api->http_code), 5); + } + + if (!isset($tokens["oauth_token"])) { + throw new Exception("Authentication failed! {$this->providerId} returned an invalid oauth token.", 5); + } + + $this->token("request_token", $tokens["oauth_token"]); + $this->token("request_token_secret", $tokens["oauth_token_secret"]); + + # redirect the user to the provider authentication url + Hybrid_Auth::redirect($this->api->authorizeUrl($tokens)); + } + + /** + * {@inheritdoc} + */ + function loginFinish() { + $oauth_token = (array_key_exists('oauth_token', $_REQUEST)) ? $_REQUEST['oauth_token'] : ""; + $oauth_verifier = (array_key_exists('oauth_verifier', $_REQUEST)) ? $_REQUEST['oauth_verifier'] : ""; + + if (!$oauth_token || !$oauth_verifier) { + throw new Exception("Authentication failed! {$this->providerId} returned an invalid oauth verifier.", 5); + } + + // request an access token + $tokens = $this->api->accessToken($oauth_verifier); + + // access tokens as received from provider + $this->access_tokens_raw = $tokens; + + // check the last HTTP status code returned + if ($this->api->http_code != 200) { + throw new Exception("Authentication failed! {$this->providerId} returned an error. " . $this->errorMessageByStatus($this->api->http_code), 5); + } + + // we should have an access_token, or else, something has gone wrong + if (!isset($tokens["oauth_token"])) { + throw new Exception("Authentication failed! {$this->providerId} returned an invalid access token.", 5); + } + + // we no more need to store request tokens + $this->deleteToken("request_token"); + $this->deleteToken("request_token_secret"); + + // store access_token for later user + $this->token("access_token", $tokens['oauth_token']); + $this->token("access_token_secret", $tokens['oauth_token_secret']); + + // set user as logged in to the current provider + $this->setUserConnected(); + } + +} diff --git a/hauth/Hybrid/Provider_Model_OAuth2.php b/hauth/Hybrid/Provider_Model_OAuth2.php new file mode 100644 index 0000000..b9de4e2 --- /dev/null +++ b/hauth/Hybrid/Provider_Model_OAuth2.php @@ -0,0 +1,184 @@ + "OK: Success!", + 304 => "Not Modified: There was no new data to return.", + 400 => "Bad Request: The request was invalid.", + 401 => "Unauthorized.", + 403 => "Forbidden: The request is understood, but it has been refused.", + 404 => "Not Found: The URI requested is invalid or the resource requested does not exists.", + 406 => "Not Acceptable.", + 500 => "Internal Server Error: Something is broken.", + 502 => "Bad Gateway.", + 503 => "Service Unavailable." + ); + + if (!$code && $this->api) { + $code = $this->api->http_code; + } + + if (isset($http_status_codes[$code])) { + return $code . " " . $http_status_codes[$code]; + } + } + + /** + * Adapter initializer + */ + 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); + } + + // 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"; + + // create a new OAuth2 client instance + $this->api = new OAuth2Client($this->config["keys"]["id"], $this->config["keys"]["secret"], $this->endpoint, $this->compressed); + + // 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 exist + if (isset(Hybrid_Auth::$config["proxy"])) { + $this->api->curl_proxy = Hybrid_Auth::$config["proxy"]; + } + } + + /** + * {@inheritdoc} + */ + function loginBegin() { + // redirect the user to the provider authentication url + Hybrid_Auth::redirect($this->api->authorizeUrl(array("scope" => $this->scope))); + } + + /** + * {@inheritdoc} + */ + function loginFinish() { + $error = (array_key_exists('error', $_REQUEST)) ? $_REQUEST['error'] : ""; + + // check for errors + if ($error) { + throw new Exception("Authentication failed! {$this->providerId} returned an error: $error", 5); + } + + // try to authenticate user + $code = (array_key_exists('code', $_REQUEST)) ? $_REQUEST['code'] : ""; + + try { + $this->api->authenticate($code); + } catch (Exception $e) { + throw new Exception("User profile request failed! {$this->providerId} returned an error: " . $e->getMessage(), 6); + } + + // check if authenticated + if (!$this->api->access_token) { + throw new Exception("Authentication failed! {$this->providerId} returned an invalid access token.", 5); + } + + // store tokens + $this->token("access_token", $this->api->access_token); + $this->token("refresh_token", $this->api->refresh_token); + $this->token("expires_in", $this->api->access_token_expires_in); + $this->token("expires_at", $this->api->access_token_expires_at); + + // set user connected locally + $this->setUserConnected(); + } + + /** + * {@inheritdoc} + */ + function refreshToken() { + // have an access token? + if ($this->api->access_token) { + + // have to refresh? + if ($this->api->refresh_token && $this->api->access_token_expires_at) { + + // expired? + if ($this->api->access_token_expires_at <= time()) { + $response = $this->api->refreshToken(array("refresh_token" => $this->api->refresh_token)); + + if (!isset($response->access_token) || !$response->access_token) { + // set the user as disconnected at this point and throw an exception + $this->setUserUnconnected(); + + throw new Exception("The Authorization Service has return an invalid response while requesting a new access token. " . (string) $response->error); + } + + // set new access_token + $this->api->access_token = $response->access_token; + + if (isset($response->refresh_token)) + $this->api->refresh_token = $response->refresh_token; + + if (isset($response->expires_in)) { + $this->api->access_token_expires_in = $response->expires_in; + + // even given by some idp, we should calculate this + $this->api->access_token_expires_at = time() + $response->expires_in; + } + } + } + + // re store tokens + $this->token("access_token", $this->api->access_token); + $this->token("refresh_token", $this->api->refresh_token); + $this->token("expires_in", $this->api->access_token_expires_in); + $this->token("expires_at", $this->api->access_token_expires_at); + } + } + +} diff --git a/hauth/Hybrid/Provider_Model_OpenID.php b/hauth/Hybrid/Provider_Model_OpenID.php new file mode 100644 index 0000000..08fa36c --- /dev/null +++ b/hauth/Hybrid/Provider_Model_OpenID.php @@ -0,0 +1,170 @@ +public $openidIdentifier = ""; + * + * Hybrid_Provider_Model_OpenID use LightOpenID lib which can be found on + * Hybrid/thirdparty/OpenID/LightOpenID.php + */ +class Hybrid_Provider_Model_OpenID extends Hybrid_Provider_Model { + + /** + * Provider API client + * @var LightOpenID + */ + public $api = null; + + /** + * Openid provider identifier + * @var string + */ + public $openidIdentifier = ""; + + /** + * {@inheritdoc} + */ + function initialize() { + if (isset($this->params["openid_identifier"])) { + $this->openidIdentifier = $this->params["openid_identifier"]; + } + + // include LightOpenID lib + require_once Hybrid_Auth::$config["path_libraries"] . "OpenID/LightOpenID.php"; + + // An error was occurring when proxy wasn't set. Not sure where proxy was meant to be set/initialized. + Hybrid_Auth::$config['proxy'] = isset(Hybrid_Auth::$config['proxy']) ? Hybrid_Auth::$config['proxy'] : ''; + + $hostPort = parse_url(Hybrid_Auth::$config["base_url"], PHP_URL_PORT); + $hostUrl = parse_url(Hybrid_Auth::$config["base_url"], PHP_URL_HOST); + + // Check for port on url + if ($hostPort) { + $hostUrl .= ':' . $hostPort; + } + + $this->api = new LightOpenID($hostUrl, Hybrid_Auth::$config["proxy"]); + } + + /** + * {@inheritdoc} + */ + function loginBegin() { + if (empty($this->openidIdentifier)) { + throw new Exception("OpenID adapter require the identity provider identifier 'openid_identifier' as an extra parameter.", 4); + } + + $this->api->identity = $this->openidIdentifier; + $this->api->returnUrl = $this->endpoint; + $this->api->required = array( + 'namePerson/first', + 'namePerson/last', + 'namePerson/friendly', + 'namePerson', + 'contact/email', + 'birthDate', + 'birthDate/birthDay', + 'birthDate/birthMonth', + 'birthDate/birthYear', + 'person/gender', + 'pref/language', + 'contact/postalCode/home', + 'contact/city/home', + 'contact/country/home', + 'media/image/default', + ); + + # redirect the user to the provider authentication url + Hybrid_Auth::redirect($this->api->authUrl()); + } + + /** + * {@inheritdoc} + */ + function loginFinish() { + # if user don't grant access of their data to your site, halt with an Exception + if ($this->api->mode == 'cancel') { + throw new Exception("Authentication failed! User has canceled authentication!", 5); + } + + # if something goes wrong + if (!$this->api->validate()) { + throw new Exception("Authentication failed. Invalid request received!", 5); + } + + # fetch received user data + $response = $this->api->getAttributes(); + + # store the user profile + $this->user->profile->identifier = $this->api->identity; + + $this->user->profile->firstName = (array_key_exists("namePerson/first", $response)) ? $response["namePerson/first"] : ""; + $this->user->profile->lastName = (array_key_exists("namePerson/last", $response)) ? $response["namePerson/last"] : ""; + $this->user->profile->displayName = (array_key_exists("namePerson", $response)) ? $response["namePerson"] : ""; + $this->user->profile->email = (array_key_exists("contact/email", $response)) ? $response["contact/email"] : ""; + $this->user->profile->language = (array_key_exists("pref/language", $response)) ? $response["pref/language"] : ""; + $this->user->profile->country = (array_key_exists("contact/country/home", $response)) ? $response["contact/country/home"] : ""; + $this->user->profile->zip = (array_key_exists("contact/postalCode/home", $response)) ? $response["contact/postalCode/home"] : ""; + $this->user->profile->gender = (array_key_exists("person/gender", $response)) ? $response["person/gender"] : ""; + $this->user->profile->photoURL = (array_key_exists("media/image/default", $response)) ? $response["media/image/default"] : ""; + + $this->user->profile->birthDay = (array_key_exists("birthDate/birthDay", $response)) ? $response["birthDate/birthDay"] : ""; + $this->user->profile->birthMonth = (array_key_exists("birthDate/birthMonth", $response)) ? $response["birthDate/birthMonth"] : ""; + $this->user->profile->birthYear = (array_key_exists("birthDate/birthDate", $response)) ? $response["birthDate/birthDate"] : ""; + + if (isset($response['namePerson/friendly']) && !empty($response['namePerson/friendly']) && !$this->user->profile->displayName) { + $this->user->profile->displayName = $response["namePerson/friendly"]; + } + + if (isset($response['birthDate']) && !empty($response['birthDate']) && !$this->user->profile->birthDay) { + list( $birthday_year, $birthday_month, $birthday_day ) = $response['birthDate']; + + $this->user->profile->birthDay = (int) $birthday_day; + $this->user->profile->birthMonth = (int) $birthday_month; + $this->user->profile->birthYear = (int) $birthday_year; + } + + if (!$this->user->profile->displayName) { + $this->user->profile->displayName = trim($this->user->profile->firstName . " " . $this->user->profile->lastName); + } + + if ($this->user->profile->gender == "f") { + $this->user->profile->gender = "female"; + } + + if ($this->user->profile->gender == "m") { + $this->user->profile->gender = "male"; + } + + // set user as logged in + $this->setUserConnected(); + + // with openid providers we get the user profile only once, so store it + Hybrid_Auth::storage()->set("hauth_session.{$this->providerId}.user", $this->user); + } + + /** + * {@inheritdoc} + */ + function getUserProfile() { + // try to get the user profile from stored data + $this->user = Hybrid_Auth::storage()->get("hauth_session.{$this->providerId}.user"); + + // if not found + if (!is_object($this->user)) { + throw new Exception("User profile request failed! User is not connected to {$this->providerId} or his session has expired.", 6); + } + + return $this->user->profile; + } + +} diff --git a/hauth/Hybrid/Providers/AOL.php b/hauth/Hybrid/Providers/AOL.php new file mode 100644 index 0000000..19028c0 --- /dev/null +++ b/hauth/Hybrid/Providers/AOL.php @@ -0,0 +1,18 @@ +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/hauth/Hybrid/Providers/Dropbox.php b/hauth/Hybrid/Providers/Dropbox.php new file mode 100644 index 0000000..cc072ab --- /dev/null +++ b/hauth/Hybrid/Providers/Dropbox.php @@ -0,0 +1,83 @@ +api->api_base_url = "https://api.dropbox.com/1/"; + $this->api->authorize_url = "https://www.dropbox.com/1/oauth2/authorize"; + $this->api->token_url = "https://api.dropbox.com/1/oauth2/token"; + } + + /** + * load the user profile from the IDp api client + */ + function getUserProfile() + { + // refresh tokens if needed + $this->refreshToken(); + + try{ + $response = $this->api->api( "account/info" ); + } + catch( DropboxException $e ){ + throw new Exception( "User profile request failed! {$this->providerId} returned an error: $e", 6 ); + } + + // check the last HTTP status code returned + if ( $this->api->http_code != 200 ){ + throw new Exception( "User profile request failed! {$this->providerId} returned an error. " . $this->errorMessageByStatus( $this->api->http_code ), 6 ); + } + + if ( ! is_object( $response ) || ! isset( $response->uid ) ){ + throw new Exception( "User profile request failed! {$this->providerId} api returned an invalid response.", 6 ); + } + # store the user profile. + $this->user->profile->identifier = (property_exists($response,'uid'))?$response->uid:""; + $this->user->profile->profileURL = ""; + $this->user->profile->webSiteURL = ""; + $this->user->profile->photoURL = ""; + $this->user->profile->displayName = (property_exists($response,'display_name'))?$response->display_name:""; + $this->user->profile->description = ""; + $this->user->profile->firstName = (property_exists($response,'name_details'))?(property_exists($response->name_details,'given_name'))?$response->name_details->given_name:"":""; + $this->user->profile->lastName = (property_exists($response,'name_details'))?(property_exists($response->name_details,'surname'))?$response->name_details->surname:"":""; + $this->user->profile->gender = ""; + $this->user->profile->language = ""; + $this->user->profile->age = ""; + $this->user->profile->birthDay = ""; + $this->user->profile->birthMonth = ""; + $this->user->profile->birthYear = ""; + $this->user->profile->email = (property_exists($response,'email'))?$response->email:""; + $this->user->profile->emailVerified = ""; + if ( property_exists($response,'email_verified') ) { + if ( $response->email_verified ) { + $this->user->profile->emailVerified = $this->user->profile->email; + } + } + $this->user->profile->phone = ""; + $this->user->profile->address = ""; + $this->user->profile->country = (property_exists($response,'country'))?$response->country:""; + $this->user->profile->region = ""; + $this->user->profile->city = ""; + $this->user->profile->zip = ""; + + return $this->user->profile; + } +} diff --git a/hauth/Hybrid/Providers/Facebook.php b/hauth/Hybrid/Providers/Facebook.php new file mode 100644 index 0000000..00cf937 --- /dev/null +++ b/hauth/Hybrid/Providers/Facebook.php @@ -0,0 +1,393 @@ +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; + + // Check if there is Graph SDK in thirdparty/Facebook. + if (file_exists(Hybrid_Auth::$config["path_libraries"] . "Facebook/autoload.php")) { + require_once Hybrid_Auth::$config["path_libraries"] . "Facebook/autoload.php"; + } + else { + // If Composer install was executed, try to find autoload.php. + $vendorDir = dirname(Hybrid_Auth::$config['path_base']); + do { + if (file_exists($vendorDir . "/vendor/autoload.php")) { + require_once $vendorDir . "/vendor/autoload.php"; + break; + } + } while (($vendorDir = dirname($vendorDir)) !== '/'); + } + + $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($this->params['login_done']); + } 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(); + } + + /** + * 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); + } + + $access_token = null; + + if (is_null($pageid)) { + $pageid = 'me'; + $access_token = $this->token('access_token'); + + // if post on page, get access_token page + } else { + + 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!"); + } + } + + try { + $response = $this->api->post('/' . $pageid . '/feed', $status, $access_token); + } catch (FacebookSDKException $e) { + throw new Exception("Update user status failed! {$this->providerId} returned an error {$e->getMessage()}", 0, $e); + } + + return $response; + } + + /** + * {@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->get("/me/accounts", $this->token('access_token')); + $pages = $pages->getDecodedBody(); + } 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; + } + + /** + * {@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(end($regionArr)); + } + } + + 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')); + } + $response = $response->getDecodedBody(); + } 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/hauth/Hybrid/Providers/Foursquare.php b/hauth/Hybrid/Providers/Foursquare.php new file mode 100644 index 0000000..5c64e96 --- /dev/null +++ b/hauth/Hybrid/Providers/Foursquare.php @@ -0,0 +1,121 @@ + array ( + * "enabled" => true, + * "keys" => ..., + * "params" => array( "photo_size" => "16x16" ) + * ), + * ... + * - list of valid photo_size values is described here https://developer.foursquare.com/docs/responses/photo.html + * - default photo_size is 100x100 + */ +class Hybrid_Providers_Foursquare extends Hybrid_Provider_Model_OAuth2 { + + private static $apiVersion = array("v" => "20120610"); + private static $defPhotoSize = "100x100"; + + /** + * {@inheritdoc} + */ + function initialize() { + parent::initialize(); + + // Provider apis end-points + $this->api->api_base_url = "https://api.foursquare.com/v2/"; + $this->api->authorize_url = "https://foursquare.com/oauth2/authenticate"; + $this->api->token_url = "https://foursquare.com/oauth2/access_token"; + + $this->api->sign_token_name = "oauth_token"; + } + + /** + * {@inheritdoc} + */ + function getUserProfile() { + $data = $this->api->api("users/self", "GET", Hybrid_Providers_Foursquare::$apiVersion); + + if (!isset($data->response->user->id)) { + throw new Exception("User profile request failed! {$this->providerId} returned an invalid response:" . Hybrid_Logger::dumpData( $data ), 6); + } + + $data = $data->response->user; + + $this->user->profile->identifier = $data->id; + $this->user->profile->firstName = $data->firstName; + $this->user->profile->lastName = $data->lastName; + $this->user->profile->displayName = $this->buildDisplayName($this->user->profile->firstName, $this->user->profile->lastName); + $this->user->profile->photoURL = $this->buildPhotoURL($data->photo->prefix, $data->photo->suffix); + $this->user->profile->profileURL = "https://www.foursquare.com/user/" . $data->id; + $this->user->profile->gender = $data->gender; + $this->user->profile->city = $data->homeCity; + $this->user->profile->email = $data->contact->email; + $this->user->profile->emailVerified = $data->contact->email; + + return $this->user->profile; + } + + /** + * {@inheritdoc} + */ + function getUserContacts() { + // refresh tokens if needed + $this->refreshToken(); + + // + $response = array(); + $contacts = array(); + try { + $response = $this->api->api("users/self/friends", "GET", Hybrid_Providers_Foursquare::$apiVersion); + } catch (Exception $e) { + throw new Exception("User contacts request failed! {$this->providerId} returned an error: {$e->getMessage()}", 0, $e); + } + + if (isset($response) && $response->meta->code == 200) { + foreach ($response->response->friends->items as $contact) { + $uc = new Hybrid_User_Contact(); + // + $uc->identifier = $contact->id; + //$uc->profileURL = ; + //$uc->webSiteURL = ; + $uc->photoURL = $this->buildPhotoURL($contact->photo->prefix, $contact->photo->suffix); + $uc->displayName = $this->buildDisplayName((isset($contact->firstName) ? ($contact->firstName) : ("")), (isset($contact->lastName) ? ($contact->lastName) : (""))); + //$uc->description = ; + $uc->email = (isset($contact->contact->email) ? ($contact->contact->email) : ("")); + // + $contacts[] = $uc; + } + } + return $contacts; + } + + /** + * {@inheritdoc} + */ + private function buildDisplayName($firstName, $lastName) { + return trim($firstName . " " . $lastName); + } + + private function buildPhotoURL($prefix, $suffix) { + if (isset($prefix) && isset($suffix)) { + return $prefix . ((isset($this->config["params"]["photo_size"])) ? ($this->config["params"]["photo_size"]) : (Hybrid_Providers_Foursquare::$defPhotoSize)) . $suffix; + } + return (""); + } + +} diff --git a/hauth/Hybrid/Providers/Google.php b/hauth/Hybrid/Providers/Google.php new file mode 100644 index 0000000..7635880 --- /dev/null +++ b/hauth/Hybrid/Providers/Google.php @@ -0,0 +1,306 @@ + more infos on google APIs: http://developer.google.com (official site) + * or here: http://discovery-check.appspot.com/ (unofficial but up to date) + * default permissions + * {@inheritdoc} + */ + public $scope = "https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/plus.profile.emails.read https://www.google.com/m8/feeds/"; + + /** + * {@inheritdoc} + */ + function initialize() { + parent::initialize(); + + // Provider api end-points + $this->api->authorize_url = "https://accounts.google.com/o/oauth2/auth"; + $this->api->token_url = "https://accounts.google.com/o/oauth2/token"; + $this->api->token_info_url = "https://www.googleapis.com/oauth2/v2/tokeninfo"; + + // Google POST methods require an access_token in the header + $this->api->curl_header = array("Authorization: OAuth " . $this->api->access_token); + + // Override the redirect uri when it's set in the config parameters. This way we prevent + // redirect uri mismatches when authenticating with Google. + if (isset($this->config['redirect_uri']) && !empty($this->config['redirect_uri'])) { + $this->api->redirect_uri = $this->config['redirect_uri']; + } + } + + /** + * {@inheritdoc} + */ + function loginBegin() { + $parameters = array("scope" => $this->scope, "access_type" => "offline"); + $optionals = array("scope", "access_type", "redirect_uri", "approval_prompt", "hd", "state"); + + foreach ($optionals as $parameter) { + if (isset($this->config[$parameter]) && !empty($this->config[$parameter])) { + $parameters[$parameter] = $this->config[$parameter]; + } + if (isset($this->config["scope"]) && !empty($this->config["scope"])) { + $this->scope = $this->config["scope"]; + } + } + + if (isset($this->config['force']) && $this->config['force'] === true) { + $parameters['approval_prompt'] = 'force'; + } + + Hybrid_Auth::redirect($this->api->authorizeUrl($parameters)); + } + + /** + * {@inheritdoc} + */ + function getUserProfile() { + // refresh tokens if needed + $this->refreshToken(); + + // ask google api for user infos + if (strpos($this->scope, '/auth/plus.profile.emails.read') !== false) { + $verified = $this->api->api("https://www.googleapis.com/plus/v1/people/me"); + + if (!isset($verified->id) || isset($verified->error)) + $verified = new stdClass(); + } else { + $verified = $this->api->api("https://www.googleapis.com/plus/v1/people/me/openIdConnect"); + + if (!isset($verified->sub) || isset($verified->error)) + $verified = new stdClass(); + } + + $response = $this->api->api("https://www.googleapis.com/plus/v1/people/me"); + if (!isset($response->id) || isset($response->error)) { + throw new Exception("User profile request failed! {$this->providerId} returned an invalid response:" . Hybrid_Logger::dumpData( $response ), 6); + } + + $this->user->profile->identifier = (property_exists($verified, 'id')) ? $verified->id : ((property_exists($response, 'id')) ? $response->id : ""); + $this->user->profile->firstName = (property_exists($response, 'name')) ? $response->name->givenName : ""; + $this->user->profile->lastName = (property_exists($response, 'name')) ? $response->name->familyName : ""; + $this->user->profile->displayName = (property_exists($response, 'displayName')) ? $response->displayName : ""; + $this->user->profile->photoURL = (property_exists($response, 'image')) ? ((property_exists($response->image, 'url')) ? substr($response->image->url, 0, -2) . "200" : '') : ''; + $this->user->profile->profileURL = (property_exists($response, 'url')) ? $response->url : ""; + $this->user->profile->description = (property_exists($response, 'aboutMe')) ? $response->aboutMe : ""; + $this->user->profile->gender = (property_exists($response, 'gender')) ? $response->gender : ""; + $this->user->profile->language = (property_exists($response, 'locale')) ? $response->locale : ((property_exists($verified, 'locale')) ? $verified->locale : ""); + $this->user->profile->email = (property_exists($response, 'email')) ? $response->email : ((property_exists($verified, 'email')) ? $verified->email : ""); + $this->user->profile->emailVerified = (property_exists($verified, 'email')) ? $verified->email : ""; + if (property_exists($response, 'emails')) { + if (count($response->emails) == 1) { + $this->user->profile->email = $response->emails[0]->value; + } else { + foreach ($response->emails as $email) { + if ($email->type == 'account') { + $this->user->profile->email = $email->value; + break; + } + } + } + if (property_exists($verified, 'emails')) { + if (count($verified->emails) == 1) { + $this->user->profile->emailVerified = $verified->emails[0]->value; + } else { + foreach ($verified->emails as $email) { + if ($email->type == 'account') { + $this->user->profile->emailVerified = $email->value; + break; + } + } + } + } + } + $this->user->profile->phone = (property_exists($response, 'phone')) ? $response->phone : ""; + $this->user->profile->country = (property_exists($response, 'country')) ? $response->country : ""; + $this->user->profile->region = (property_exists($response, 'region')) ? $response->region : ""; + $this->user->profile->zip = (property_exists($response, 'zip')) ? $response->zip : ""; + if (property_exists($response, 'placesLived')) { + $this->user->profile->city = ""; + $this->user->profile->address = ""; + foreach ($response->placesLived as $c) { + if (property_exists($c, 'primary')) { + if ($c->primary == true) { + $this->user->profile->address = $c->value; + $this->user->profile->city = $c->value; + break; + } + } else { + if (property_exists($c, 'value')) { + $this->user->profile->address = $c->value; + $this->user->profile->city = $c->value; + } + } + } + } + + // google API returns multiple urls, but a "website" only if it is verified + // see http://support.google.com/plus/answer/1713826?hl=en + if (property_exists($response, 'urls')) { + foreach ($response->urls as $u) { + if (property_exists($u, 'primary') && $u->primary == true) + $this->user->profile->webSiteURL = $u->value; + } + } else { + $this->user->profile->webSiteURL = ''; + } + // 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 { + 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 = ''; + } + // google API returns birthdays only if a user set 'show in my account' + if (property_exists($response, 'birthday')) { + list($birthday_year, $birthday_month, $birthday_day) = explode('-', $response->birthday); + + $this->user->profile->birthDay = (int) $birthday_day; + $this->user->profile->birthMonth = (int) $birthday_month; + $this->user->profile->birthYear = (int) $birthday_year; + } else { + $this->user->profile->birthDay = 0; + $this->user->profile->birthMonth = 0; + $this->user->profile->birthYear = 0; + } + + return $this->user->profile; + } + + /** + * {@inheritdoc} + */ + function getUserContacts() { + // refresh tokens if needed + $this->refreshToken(); + + $contacts = array(); + if (!isset($this->config['contacts_param'])) { + $this->config['contacts_param'] = array("max-results" => 500); + } + + // Google Gmail and Android contacts + if (strpos($this->scope, '/m8/feeds/') !== false) { + + $response = $this->api->api("https://www.google.com/m8/feeds/contacts/default/full?" + . http_build_query(array_merge(array('alt' => 'json'), $this->config['contacts_param']))); + + if (!$response) { + return array(); + } + + if (isset($response->feed->entry)) { + foreach ($response->feed->entry as $idx => $entry) { + $uc = new Hybrid_User_Contact(); + $uc->email = isset($entry->{'gd$email'}[0]->address) ? (string) $entry->{'gd$email'}[0]->address : ''; + $uc->displayName = isset($entry->title->{'$t'}) ? (string) $entry->title->{'$t'} : ''; + $uc->identifier = ($uc->email != '') ? $uc->email : ''; + $uc->description = ''; + if (property_exists($entry, 'link')) { + /** + * sign links with access_token + */ + if (is_array($entry->link)) { + foreach ($entry->link as $l) { + if (property_exists($l, 'gd$etag') && $l->type == "image/*") { + $uc->photoURL = $this->addUrlParam($l->href, array('access_token' => $this->api->access_token)); + } else if ($l->type == "self") { + $uc->profileURL = $this->addUrlParam($l->href, array('access_token' => $this->api->access_token)); + } + } + } + } else { + $uc->profileURL = ''; + } + if (property_exists($response, 'website')) { + if (is_array($response->website)) { + foreach ($response->website as $w) { + if ($w->primary == true) + $uc->webSiteURL = $w->value; + } + } else { + $uc->webSiteURL = $response->website->value; + } + } else { + $uc->webSiteURL = ''; + } + + $contacts[] = $uc; + } + } + } + + // Google social contacts + if (strpos($this->scope, '/auth/plus.login') !== false) { + + $response = $this->api->api("https://www.googleapis.com/plus/v1/people/me/people/visible?" + . http_build_query($this->config['contacts_param'])); + + if (!$response) { + return array(); + } + + foreach ($response->items as $idx => $item) { + $uc = new Hybrid_User_Contact(); + $uc->email = (property_exists($item, 'email')) ? $item->email : ''; + $uc->displayName = (property_exists($item, 'displayName')) ? $item->displayName : ''; + $uc->identifier = (property_exists($item, 'id')) ? $item->id : ''; + + $uc->description = (property_exists($item, 'objectType')) ? $item->objectType : ''; + $uc->photoURL = (property_exists($item, 'image')) ? ((property_exists($item->image, 'url')) ? $item->image->url : '') : ''; + $uc->profileURL = (property_exists($item, 'url')) ? $item->url : ''; + $uc->webSiteURL = ''; + + $contacts[] = $uc; + } + } + + return $contacts; + } + + /** + * Add query parameters to the $url + * + * @param string $url URL + * @param array $params Parameters to add + * @return string + */ + function addUrlParam($url, array $params){ + $query = parse_url($url, PHP_URL_QUERY); + + // Returns the URL string with new parameters + if ($query) { + $url .= '&' . http_build_query($params); + } else { + $url .= '?' . http_build_query($params); + } + return $url; + } + +} + diff --git a/hauth/Hybrid/Providers/Instagram.php b/hauth/Hybrid/Providers/Instagram.php new file mode 100644 index 0000000..3f958a0 --- /dev/null +++ b/hauth/Hybrid/Providers/Instagram.php @@ -0,0 +1,91 @@ +api->api_base_url = "https://api.instagram.com/v1/"; + $this->api->authorize_url = "https://api.instagram.com/oauth/authorize/"; + $this->api->token_url = "https://api.instagram.com/oauth/access_token"; + } + + /** + * load the user profile from the IDp api client + */ + function getUserProfile(){ + $data = $this->api->api("users/self/" ); + + if ( $data->meta->code != 200 ){ + throw new Exception( "User profile request failed! {$this->providerId} returned an invalid response.", 6 ); + } + + $this->user->profile->identifier = $data->data->id; + $this->user->profile->displayName = $data->data->full_name ? $data->data->full_name : $data->data->username; + $this->user->profile->description = $data->data->bio; + $this->user->profile->photoURL = $data->data->profile_picture; + + $this->user->profile->webSiteURL = $data->data->website; + + $this->user->profile->username = $data->data->username; + + return $this->user->profile; + } + /** + * + */ + function getUserContacts() { + // refresh tokens if needed + $this->refreshToken(); + + // + $response = array(); + $contacts = array(); + $profile = ( ( isset( $this->user->profile->identifier ) )?( $this->user->profile ):( $this->getUserProfile() ) ); + try { + $response = $this->api->api( "users/{$this->user->profile->identifier}/follows" ); + } catch (Exception $e) { + throw new Exception("User contacts request failed! {$this->providerId} returned an error: $e"); + } + // + + if ( isset( $response ) && $response->meta->code == 200 ) { + foreach ($response->data as $contact) { + try { + $contactInfo = $this->api->api( "users/".$contact->id ); + } catch (Exception $e) { + throw new Exception("Contact info request failed for user {$contact->username}! {$this->providerId} returned an error: $e"); + } + // + $uc = new Hybrid_User_Contact(); + // + $uc->identifier = $contact->id; + $uc->profileURL = "https://instagram.com/{$contact->username}"; + $uc->webSiteURL = @$contactInfo->data->website; + $uc->photoURL = @$contact->profile_picture; + $uc->displayName = @$contact->full_name; + $uc->description = @$contactInfo->data->bio; + //$uc->email = ; + // + $contacts[] = $uc; + } + } + return $contacts; + } +} diff --git a/hauth/Hybrid/Providers/LinkedIn.php b/hauth/Hybrid/Providers/LinkedIn.php new file mode 100644 index 0000000..2a442b2 --- /dev/null +++ b/hauth/Hybrid/Providers/LinkedIn.php @@ -0,0 +1,170 @@ +api->api_base_url = "https://api.linkedin.com/v1/"; + $this->api->authorize_url = "https://www.linkedin.com/oauth/v2/authorization"; + $this->api->token_url = "https://www.linkedin.com/oauth/v2/accessToken"; + } + + /** + * {@inheritdoc} + */ + function loginBegin() { + if (is_array($this->scope)) { + $this->scope = implode(" ", $this->scope); + } + parent::loginBegin(); + } + + /** + * {@inheritdoc} + * + * @see https://developer.linkedin.com/docs/rest-api + */ + function getUserProfile() { + // Refresh tokens if needed. + $this->setHeaders("token"); + $this->refreshToken(); + + // https://developer.linkedin.com/docs/fields. + $fields = isset($this->config["fields"]) ? $this->config["fields"] : [ + "id", + "email-address", + "first-name", + "last-name", + "headline", + "location", + "industry", + "picture-url", + "public-profile-url", + ]; + + $this->setHeaders(); + $response = $this->api->get( + "people/~:(" . implode(",", $fields) . ")", + array( + "format" => "json", + ) + ); + + if (!isset($response->id)) { + throw new Exception("User profile request failed! {$this->providerId} returned an invalid response: " . Hybrid_Logger::dumpData($response), 6); + } + + $this->user->profile->identifier = isset($response->id) ? $response->id : ""; + $this->user->profile->firstName = isset($response->firstName) ? $response->firstName : ""; + $this->user->profile->lastName = isset($response->lastName) ? $response->lastName : ""; + $this->user->profile->photoURL = isset($response->pictureUrl) ? $response->pictureUrl : ""; + $this->user->profile->profileURL = isset($response->publicProfileUrl) ? $response->publicProfileUrl : ""; + $this->user->profile->email = isset($response->emailAddress) ? $response->emailAddress : ""; + $this->user->profile->description = isset($response->headline) ? $response->headline : ""; + $this->user->profile->country = isset($response->location) ? $response->location->name : ""; + $this->user->profile->emailVerified = $this->user->profile->email; + $this->user->profile->displayName = trim($this->user->profile->firstName . " " . $this->user->profile->lastName); + + return $this->user->profile; + } + + /** + * {@inheritdoc} + * + * @param array $status + * An associative array containing: + * - content: A collection of fields describing the shared content. + * - comment: A comment by the member to associated with the share. + * - visibility: A collection of visibility information about the share. + * + * @return object + * An object containing: + * - updateKey - A unique ID for the shared content posting that was just created. + * - updateUrl - A direct link to the newly shared content on LinkedIn.com that you can direct the user's web browser to. + * @throws Exception + * @see https://developer.linkedin.com/docs/share-on-linkedin + */ + function setUserStatus($status) { + // Refresh tokens if needed. + $this->setHeaders("token"); + $this->refreshToken(); + + try { + // Define default visibility. + if (!isset($status["visibility"])) { + $status["visibility"]["code"] = "anyone"; + } + + $this->setHeaders("share"); + $response = $this->api->post( + "people/~/shares?format=json", + array( + "body" => $status, + ) + ); + } catch (Exception $e) { + throw new Exception("Update user status failed! {$this->providerId} returned an error: {$e->getMessage()}", 0, $e); + } + + if (!isset($response->updateKey)) { + throw new Exception("Update user status failed! {$this->providerId} returned an error: {$response->message}", $response->errorCode); + } + + return $response; + } + + /** + * Set correct request headers. + * + * @param string $api_type + * (optional) Specify api type. + * + * @return void + */ + private function setHeaders($api_type = null) { + $this->api->curl_header = array( + "Authorization: Bearer {$this->api->access_token}", + ); + + switch ($api_type) { + case "share": + $this->api->curl_header = array_merge( + $this->api->curl_header, + array( + "Content-Type: application/json", + "x-li-format: json", + ) + ); + break; + + case "token": + $this->api->curl_header = array_merge( + $this->api->curl_header, + array( + "Content-Type: application/x-www-form-urlencoded", + ) + ); + break; + } + } + +} diff --git a/hauth/Hybrid/Providers/Live.php b/hauth/Hybrid/Providers/Live.php new file mode 100644 index 0000000..6081feb --- /dev/null +++ b/hauth/Hybrid/Providers/Live.php @@ -0,0 +1,108 @@ + + * @version 0.2 + * @license BSD License + */ + +/** + * Hybrid_Providers_Live - Windows Live provider adapter based on OAuth2 protocol + */ +class Hybrid_Providers_Live extends Hybrid_Provider_Model_OAuth2 { + + /** + * {@inheritdoc} + */ + public $scope = "wl.basic wl.contacts_emails wl.emails wl.signin wl.share wl.birthday"; + + /** + * {@inheritdoc} + */ + function initialize() { + parent::initialize(); + + // Provider api end-points + $this->api->api_base_url = 'https://apis.live.net/v5.0/'; + $this->api->authorize_url = 'https://oauth.live.com/authorize'; + $this->api->token_url = 'https://login.live.com/oauth20_token.srf'; + + $this->api->curl_authenticate_method = "GET"; + + // Override the redirect uri when it's set in the config parameters. This way we prevent + // redirect uri mismatches when authenticating with Live.com + if (isset($this->config['redirect_uri']) && !empty($this->config['redirect_uri'])) { + $this->api->redirect_uri = $this->config['redirect_uri']; + } + } + + /** + * {@inheritdoc} + */ + function getUserProfile() { + $data = $this->api->get("me"); + + if (!isset($data->id)) { + throw new Exception("User profile request failed! {$this->providerId} returned an invalid response: " . Hybrid_Logger::dumpData( $data ), 6); + } + + $this->user->profile->identifier = (property_exists($data, 'id')) ? $data->id : ""; + $this->user->profile->firstName = (property_exists($data, 'first_name')) ? $data->first_name : ""; + $this->user->profile->lastName = (property_exists($data, 'last_name')) ? $data->last_name : ""; + $this->user->profile->displayName = (property_exists($data, 'name')) ? trim($data->name) : ""; + $this->user->profile->gender = (property_exists($data, 'gender')) ? $data->gender : ""; + + //wl.basic + $this->user->profile->profileURL = (property_exists($data, 'link')) ? $data->link : ""; + + //wl.emails + $this->user->profile->email = (property_exists($data, 'emails')) ? $data->emails->account : ""; + $this->user->profile->emailVerified = (property_exists($data, 'emails')) ? $data->emails->account : ""; + + //wl.birthday + $this->user->profile->birthDay = (property_exists($data, 'birth_day')) ? $data->birth_day : ""; + $this->user->profile->birthMonth = (property_exists($data, 'birth_month')) ? $data->birth_month : ""; + $this->user->profile->birthYear = (property_exists($data, 'birth_year')) ? $data->birth_year : ""; + + return $this->user->profile; + } + + /** + * Windows Live api does not support retrieval of email addresses (only hashes :/) + * {@inheritdoc} + */ + function getUserContacts() { + $response = $this->api->get('me/contacts'); + + if ($this->api->http_code != 200) { + throw new Exception('User contacts request failed! ' . $this->providerId . ' returned an error: ' . $this->errorMessageByStatus($this->api->http_code)); + } + + if (!isset($response->data) || ( isset($response->errcode) && $response->errcode != 0 )) { + return array(); + } + + $contacts = array(); + + foreach ($response->data as $item) { + $uc = new Hybrid_User_Contact(); + + $uc->identifier = (property_exists($item, 'id')) ? $item->id : ""; + $uc->displayName = (property_exists($item, 'name')) ? $item->name : ""; + $uc->email = (property_exists($item, 'emails')) ? $item->emails->preferred : ""; + $contacts[] = $uc; + } + + return $contacts; + } + +} diff --git a/hauth/Hybrid/Providers/OpenID.php b/hauth/Hybrid/Providers/OpenID.php new file mode 100644 index 0000000..8f7903c --- /dev/null +++ b/hauth/Hybrid/Providers/OpenID.php @@ -0,0 +1,16 @@ + + * @version 0.2 + * @license BSD License + */ + +/** + * Hybrid_Providers_Paypal - PayPal provider adapter based on OAuth2 protocol + */ +class Hybrid_Providers_Paypal extends Hybrid_Provider_Model_OAuth2 +{ + // default permissions + public $scope = "profile email address phone https://uri.paypal.com/services/paypalattributes"; + + public $sandbox = true; + + /** + * IDp wrappers initializer + */ + 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 ); + } + + // override requested scope + if( isset( $this->config["scope"] ) && ! empty( $this->config["scope"] ) ){ + $this->scope = $this->config["scope"]; + } + + // include OAuth2 client and Paypal client + require_once Hybrid_Auth::$config["path_libraries"] . "OAuth/OAuth2Client.php"; + require_once Hybrid_Auth::$config["path_libraries"] . "Paypal/PaypalOAuth2Client.php"; + + // create a new OAuth2 client instance + $this->api = new PaypalOAuth2Client( $this->config["keys"]["id"], $this->config["keys"]["secret"], $this->endpoint ); + + // 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 exist + if( isset( Hybrid_Auth::$config["proxy"] ) ){ + $this->api->curl_proxy = Hybrid_Auth::$config["proxy"]; + } + + // Provider api end-points + if ($this->sandbox) { + $this->api->authorize_url = "https://www.sandbox.paypal.com/webapps/auth/protocol/openidconnect/v1/authorize"; + $this->api->token_url = "https://api.sandbox.paypal.com/v1/oauth2/token"; + $this->api->token_info_url = "https://api.sandbox.paypal.com/v1/identity/openidconnect/tokenservice"; + } else { + $this->api->authorize_url = "https://www.paypal.com/webapps/auth/protocol/openidconnect/v1/authorize"; + $this->api->token_url = "https://api.paypal.com/v1/oauth2/token"; + $this->api->token_info_url = "https://api.paypal.com/v1/identity/openidconnect/tokenservice"; + } + + if (Hybrid_Auth::$config["debug_mode"]) { + $this->api->curl_log = Hybrid_Auth::$config["debug_file"]; + } + } + + /** + * begin login step + */ + /*function loginBegin() + { + $parameters = array("scope" => $this->scope, "grant_type" => "client_credentials"); + $optionals = array("scope", "access_type", "redirect_uri", "approval_prompt", "hd"); + + foreach ($optionals as $parameter){ + if( isset( $this->config[$parameter] ) && ! empty( $this->config[$parameter] ) ){ + $parameters[$parameter] = $this->config[$parameter]; + } + } + + Hybrid_Auth::redirect( $this->api->authorizeUrl( $parameters ) ); + }*/ + + /** + * load the user profile from the IDp api client + */ + function getUserProfile() + { + // refresh tokens if needed + $this->refreshToken(); + + // ask google api for user infos + $response = $this->api->api( "https://api".($this->sandbox?'.sandbox' : '').".paypal.com/v1/identity/openidconnect/userinfo/?schema=openid" ); + + if ( ! isset( $response->payer_id ) || isset( $response->message ) ){ + throw new Exception( "User profile request failed! {$this->providerId} returned an invalid response.", 6 ); + } + + $this->user->profile->identifier = (property_exists($response,'payer_id'))?$response->payer_id:""; + $this->user->profile->firstName = (property_exists($response,'given_name'))?$response->given_name:""; + $this->user->profile->lastName = (property_exists($response,'family_name'))?$response->family_name:""; + $this->user->profile->displayName = (property_exists($response,'name'))?$response->name:""; + $this->user->profile->photoURL = (property_exists($response,'picture'))?$response->picture:""; + $this->user->profile->gender = (property_exists($response,'gender'))?$response->gender:""; + $this->user->profile->email = (property_exists($response,'email'))?$response->email:""; + $this->user->profile->emailVerified = (property_exists($response,'email_verified'))?$response->email_verified:""; + $this->user->profile->language = (property_exists($response,'locale'))?$response->locale:""; + $this->user->profile->phone = (property_exists($response,'phone_number'))?$response->phone_number:""; + if (property_exists($response,'address')) { + $address = $response->address; + $this->user->profile->address = (property_exists($address,'street_address'))?$address->street_address:""; + $this->user->profile->city = (property_exists($address,'locality'))?$address->locality:""; + $this->user->profile->zip = (property_exists($address,'postal_code'))?$address->postal_code:""; + $this->user->profile->country = (property_exists($address,'country'))?$address->country:""; + $this->user->profile->region = (property_exists($address,'region'))?$address->region:""; + } + + if( property_exists($response,'birthdate') ){ + if (strpos($response->birthdate, '-') === false) { + if ($response->birthdate !== '0000') { + $this->user->profile->birthYear = (int) $response->birthdate; + } + } else { + list($birthday_year, $birthday_month, $birthday_day) = explode( '-', $response->birthdate ); + + $this->user->profile->birthDay = (int) $birthday_day; + $this->user->profile->birthMonth = (int) $birthday_month; + if ($birthday_year !== '0000') { + $this->user->profile->birthYear = (int) $birthday_year; + } + } + } + + return $this->user->profile; + } +} diff --git a/hauth/Hybrid/Providers/Twitter.php b/hauth/Hybrid/Providers/Twitter.php new file mode 100644 index 0000000..6ea6231 --- /dev/null +++ b/hauth/Hybrid/Providers/Twitter.php @@ -0,0 +1,264 @@ +api->api_base_url = "https://api.twitter.com/1.1/"; + $this->api->authorize_url = "https://api.twitter.com/oauth/authenticate"; + $this->api->request_token_url = "https://api.twitter.com/oauth/request_token"; + $this->api->access_token_url = "https://api.twitter.com/oauth/access_token"; + + if (isset($this->config['api_version']) && $this->config['api_version']) { + $this->api->api_base_url = "https://api.twitter.com/{$this->config['api_version']}/"; + } + + if (isset($this->config['authorize']) && $this->config['authorize']) { + $this->api->authorize_url = "https://api.twitter.com/oauth/authorize"; + } + + $this->api->curl_auth_header = false; + } + + /** + * {@inheritdoc} + */ + function loginBegin() { + // Initiate the Reverse Auth flow; cf. https://dev.twitter.com/docs/ios/using-reverse-auth + if (isset($_REQUEST['reverse_auth']) && ($_REQUEST['reverse_auth'] == 'yes')) { + $stage1 = $this->api->signedRequest($this->api->request_token_url, 'POST', array('x_auth_mode' => 'reverse_auth')); + if ($this->api->http_code != 200) { + throw new Exception("Authentication failed! {$this->providerId} returned an error. " . $this->errorMessageByStatus($this->api->http_code), 5); + } + $responseObj = array('x_reverse_auth_parameters' => $stage1, 'x_reverse_auth_target' => $this->config["keys"]["key"]); + $response = json_encode($responseObj); + header("Content-Type: application/json", true, 200); + echo $response; + die(); + } + $tokens = $this->api->requestToken($this->endpoint); + + // request tokens as received from provider + $this->request_tokens_raw = $tokens; + + // check the last HTTP status code returned + if ($this->api->http_code != 200) { + throw new Exception("Authentication failed! {$this->providerId} returned an error. " . $this->errorMessageByStatus($this->api->http_code), 5); + } + + if (!isset($tokens["oauth_token"])) { + throw new Exception("Authentication failed! {$this->providerId} returned an invalid oauth token.", 5); + } + + $this->token("request_token", $tokens["oauth_token"]); + $this->token("request_token_secret", $tokens["oauth_token_secret"]); + + // redirect the user to the provider authentication url with force_login + if (( isset($this->config['force_login']) && $this->config['force_login'] ) || ( isset($this->config['force']) && $this->config['force'] === true )) { + Hybrid_Auth::redirect($this->api->authorizeUrl($tokens, array('force_login' => true))); + } + + // else, redirect the user to the provider authentication url + Hybrid_Auth::redirect($this->api->authorizeUrl($tokens)); + } + + /** + * {@inheritdoc} + */ + function loginFinish() { + // in case we are completing a Reverse Auth flow; cf. https://dev.twitter.com/docs/ios/using-reverse-auth + if (isset($_REQUEST['oauth_token_secret'])) { + $tokens = $_REQUEST; + $this->access_tokens_raw = $tokens; + + // we should have an access_token unless something has gone wrong + if (!isset($tokens["oauth_token"])) { + throw new Exception("Authentication failed! {$this->providerId} returned an invalid access token.", 5); + } + + // Get rid of tokens we don't need + $this->deleteToken("request_token"); + $this->deleteToken("request_token_secret"); + + // Store access_token and secret for later use + $this->token("access_token", $tokens['oauth_token']); + $this->token("access_token_secret", $tokens['oauth_token_secret']); + + // set user as logged in to the current provider + $this->setUserConnected(); + return; + } + parent::loginFinish(); + } + + /** + * {@inheritdoc} + */ + function getUserProfile() { + $includeEmail = isset($this->config['includeEmail']) ? (bool) $this->config['includeEmail'] : false; + $response = $this->api->get('account/verify_credentials.json'. ($includeEmail ? '?include_email=true' : '')); + + // check the last HTTP status code returned + if ($this->api->http_code != 200) { + throw new Exception("User profile request failed! {$this->providerId} returned an error. " . $this->errorMessageByStatus($this->api->http_code), 6); + } + + if (!is_object($response) || !isset($response->id)) { + throw new Exception("User profile request failed! {$this->providerId} api returned an invalid response: " . Hybrid_Logger::dumpData( $response ), 6); + } + + # store the user profile. + $this->user->profile->identifier = (property_exists($response, 'id')) ? $response->id : ""; + $this->user->profile->displayName = (property_exists($response, 'screen_name')) ? $response->screen_name : ""; + $this->user->profile->description = (property_exists($response, 'description')) ? $response->description : ""; + $this->user->profile->firstName = (property_exists($response, 'name')) ? $response->name : ""; + $this->user->profile->photoURL = (property_exists($response, 'profile_image_url')) ? (str_replace('_normal', '', $response->profile_image_url)) : ""; + $this->user->profile->profileURL = (property_exists($response, 'screen_name')) ? ("http://twitter.com/" . $response->screen_name) : ""; + $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; + } + + /** + * {@inheritdoc} + */ + function getUserContacts() { + $parameters = array('cursor' => '-1'); + $response = $this->api->get('friends/ids.json', $parameters); + + // check the last HTTP status code returned + if ($this->api->http_code != 200) { + throw new Exception("User contacts request failed! {$this->providerId} returned an error. " . $this->errorMessageByStatus($this->api->http_code)); + } + + if (!$response || !count($response->ids)) { + return array(); + } + + // 75 id per time should be okey + $contactsids = array_chunk($response->ids, 75); + + $contacts = array(); + + foreach ($contactsids as $chunk) { + $parameters = array('user_id' => implode(",", $chunk)); + $response = $this->api->get('users/lookup.json', $parameters); + + // check the last HTTP status code returned + if ($this->api->http_code != 200) { + throw new Exception("User contacts request failed! {$this->providerId} returned an error. " . $this->errorMessageByStatus($this->api->http_code)); + } + + if ($response && count($response)) { + foreach ($response as $item) { + $uc = new Hybrid_User_Contact(); + + $uc->identifier = (property_exists($item, 'id')) ? $item->id : ""; + $uc->displayName = (property_exists($item, 'name')) ? $item->name : ""; + $uc->profileURL = (property_exists($item, 'screen_name')) ? ("http://twitter.com/" . $item->screen_name) : ""; + $uc->photoURL = (property_exists($item, 'profile_image_url')) ? $item->profile_image_url : ""; + $uc->description = (property_exists($item, 'description')) ? $item->description : ""; + + $contacts[] = $uc; + } + } + } + + return $contacts; + } + + /** + * {@inheritdoc} + */ + function setUserStatus($status) { + + if (is_array($status) && isset($status['message']) && isset($status['picture'])) { + $response = $this->api->post('statuses/update_with_media.json', array('status' => $status['message'], 'media[]' => file_get_contents($status['picture'])), null, null, true); + } else { + $response = $this->api->post('statuses/update.json', array('status' => $status)); + } + + // check the last HTTP status code returned + if ($this->api->http_code != 200) { + throw new Exception("Update user status failed! {$this->providerId} returned an error. " . $this->errorMessageByStatus($this->api->http_code)); + } + + return $response; + } + + /** + * {@inheritdoc} + */ + function getUserStatus($tweetid) { + $info = $this->api->get('statuses/show.json?id=' . $tweetid . '&include_entities=true'); + + // check the last HTTP status code returned + if ($this->api->http_code != 200 || !isset($info->id)) { + throw new Exception("Cannot retrieve user status! {$this->providerId} returned an error. " . $this->errorMessageByStatus($this->api->http_code)); + } + + return $info; + } + + /** + * load the user latest activity + * - timeline : all the stream + * - me : the user activity only + * + * by default return the timeline + * {@inheritdoc} + */ + function getUserActivity($stream) { + if ($stream == "me") { + $response = $this->api->get('statuses/user_timeline.json'); + } else { + $response = $this->api->get('statuses/home_timeline.json'); + } + + // check the last HTTP status code returned + if ($this->api->http_code != 200) { + throw new Exception("User activity stream request failed! {$this->providerId} returned an error. " . $this->errorMessageByStatus($this->api->http_code)); + } + + if (!$response) { + return array(); + } + + $activities = array(); + + foreach ($response as $item) { + $ua = new Hybrid_User_Activity(); + + $ua->id = (property_exists($item, 'id')) ? $item->id : ""; + $ua->date = (property_exists($item, 'created_at')) ? strtotime($item->created_at) : ""; + $ua->text = (property_exists($item, 'text')) ? $item->text : ""; + + $ua->user->identifier = (property_exists($item->user, 'id')) ? $item->user->id : ""; + $ua->user->displayName = (property_exists($item->user, 'name')) ? $item->user->name : ""; + $ua->user->profileURL = (property_exists($item->user, 'screen_name')) ? ("http://twitter.com/" . $item->user->screen_name) : ""; + $ua->user->photoURL = (property_exists($item->user, 'profile_image_url')) ? $item->user->profile_image_url : ""; + + $activities[] = $ua; + } + + return $activities; + } + +} diff --git a/hauth/Hybrid/Providers/Yahoo.php b/hauth/Hybrid/Providers/Yahoo.php new file mode 100644 index 0000000..81c4828 --- /dev/null +++ b/hauth/Hybrid/Providers/Yahoo.php @@ -0,0 +1,269 @@ + + * @author Oleg Kuzava + * @version 1.0 + * @license BSD License + */ + +/** + * Hybrid_Providers_Yahoo - Yahoo provider adapter based on OAuth2 protocol. + */ +class Hybrid_Providers_Yahoo extends Hybrid_Provider_Model_OAuth2 { + + /** + * Define Yahoo scopes. + * + * @var array $scope + * If empty will be used YDN App scopes. + * @see https://developer.yahoo.com/oauth2/guide/yahoo_scopes. + */ + public $scope = []; + + /** + * {@inheritdoc} + */ + function initialize() { + parent::initialize(); + + // Provider api end-points. + $this->api->api_base_url = "https://social.yahooapis.com/v1/"; + $this->api->authorize_url = "https://api.login.yahoo.com/oauth2/request_auth"; + $this->api->token_url = "https://api.login.yahoo.com/oauth2/get_token"; + + // Set token headers. + $this->setAuthorizationHeaders("basic"); + } + + /** + * {@inheritdoc} + */ + function loginBegin() { + if (is_array($this->scope)) { + $this->scope = implode(",", $this->scope); + } + parent::loginBegin(); + } + + /** + * {@inheritdoc} + */ + function getUserProfile() { + $userId = $this->getCurrentUserId(); + + $response = $this->api->get("user/{$userId}/profile", array( + "format" => "json", + )); + + if (!isset($response->profile)) { + throw new Exception("User profile request failed! {$this->providerId} returned an invalid response: " . Hybrid_Logger::dumpData($response), 6); + } + + $data = $response->profile; + + $this->user->profile->identifier = isset($data->guid) ? $data->guid : ""; + $this->user->profile->firstName = isset($data->givenName) ? $data->givenName : ""; + $this->user->profile->lastName = isset($data->familyName) ? $data->familyName : ""; + $this->user->profile->displayName = isset($data->nickname) ? trim($data->nickname) : ""; + $this->user->profile->profileURL = isset($data->profileUrl) ? $data->profileUrl : ""; + $this->user->profile->gender = isset($data->gender) ? $data->gender : ""; + + if ($this->user->profile->gender === "F") { + $this->user->profile->gender = "female"; + } + elseif ($this->user->profile->gender === "M") { + $this->user->profile->gender = "male"; + } + + if (isset($data->emails)) { + $email = ""; + foreach ($data->emails as $v) { + if (isset($v->primary) && $v->primary) { + $email = isset($v->handle) ? $v->handle : ""; + break; + } + } + $this->user->profile->email = $email; + $this->user->profile->emailVerified = $email; + } + + $this->user->profile->age = isset($data->displayAge) ? $data->displayAge : ""; + $this->user->profile->photoURL = isset($data->image) ? $data->image->imageUrl : ""; + + $this->user->profile->address = isset($data->location) ? $data->location : ""; + $this->user->profile->language = isset($data->lang) ? $data->lang : ""; + + return $this->user->profile; + } + + /** + * {@inheritdoc} + */ + function getUserContacts() { + $userId = $this->getCurrentUserId(); + + $response = $this->api->get("user/{$userId}/contacts", array( + "format" => "json", + "count" => "max", + )); + + if ($this->api->http_code != 200) { + throw new Exception("User contacts request failed! {$this->providerId} returned an error: " . $this->errorMessageByStatus()); + } + + if (!isset($response->contacts) || !isset($response->contacts->contact) || (isset($response->errcode) && $response->errcode != 0)) { + return array(); + } + + $contacts = array(); + foreach ($response->contacts->contact as $item) { + $uc = new Hybrid_User_Contact(); + + $uc->identifier = isset($item->id) ? $item->id : ""; + $uc->email = $this->selectEmail($item->fields); + $uc->displayName = $this->selectName($item->fields); + $uc->photoURL = $this->selectPhoto($item->fields); + + $contacts[] = $uc; + } + + return $contacts; + } + + /** + * Returns current user id. + * + * @return string + * Current user ID. + * @throws Exception + */ + function getCurrentUserId() { + // Set headers to get refresh token. + $this->setAuthorizationHeaders("basic"); + + // Refresh tokens if needed. + $this->refreshToken(); + + // Set headers to make api call. + $this->setAuthorizationHeaders("bearer"); + + $response = $this->api->get("me/guid", array( + "format" => "json", + )); + + if (!isset($response->guid->value)) { + throw new Exception("User id request failed! {$this->providerId} returned an invalid response: " . Hybrid_Logger::dumpData($response)); + } + + return $response->guid->value; + } + + /** + * Utility function for returning values from XML-like objects. + * + * @param stdClass $vs + * Object. + * @param string $t + * Property name. + * @return mixed + */ + private function select($vs, $t) { + foreach ($vs as $v) { + if ($v->type == $t) { + return $v; + } + } + + return null; + } + + /** + * Parses user name. + * + * @param stdClass $v + * Object. + * @return string + * User name. + */ + private function selectName($v) { + $s = $this->select($v, "name"); + if (!$s) { + $s = $this->select($v, "nickname"); + return isset($s->value) ? $s->value : ""; + } + return isset($s->value) ? "{$s->value->givenName} {$s->value->familyName}" : ""; + } + + /** + * Parses photo URL. + * + * @param stdClass $v + * Object. + * @return string + * Photo URL. + */ + private function selectPhoto($v) { + $s = $this->select($v, "image"); + + return isset($s->value) ? $s->value->imageUrl : ""; + } + + /** + * Parses email. + * + * @param stdClass $v + * Object + * @return string + * An email address. + */ + private function selectEmail($v) { + $s = $this->select($v, "email"); + if (empty($s)) { + $s = $this->select($v, "yahooid"); + if (isset($s->value) && strpos($s->value, "@") === FALSE) { + $s->value .= "@yahoo.com"; + } + } + + return isset($s->value) ? $s->value : ""; + } + + /** + * Set correct Authorization headers. + * + * @param string $token_type + * Specify token type. + * + * @return void + */ + private function setAuthorizationHeaders($token_type) { + switch ($token_type) { + case "basic": + // The /get_token requires authorization header. + $token = base64_encode("{$this->config["keys"]["id"]}:{$this->config["keys"]["secret"]}"); + $this->api->curl_header = array( + "Authorization: Basic {$token}", + "Content-Type: application/x-www-form-urlencoded", + ); + break; + + case "bearer": + // Yahoo API requires the token to be passed as a Bearer within the authorization header. + $this->api->curl_header = array( + "Authorization: Bearer {$this->api->access_token}", + ); + break; + } + } + +} diff --git a/hauth/Hybrid/Storage.php b/hauth/Hybrid/Storage.php new file mode 100644 index 0000000..d82b4af --- /dev/null +++ b/hauth/Hybrid/Storage.php @@ -0,0 +1,141 @@ +config("php_session_id", session_id()); + $this->config("version", Hybrid_Auth::$version); + } + + /** + * Saves a value in the config storage, or returns config if value is null + * + * @param string $key Config name + * @param string $value Config value + * @return array|null + */ + public function config($key, $value = null) { + $key = strtolower($key); + + if ($value) { + $_SESSION["HA::CONFIG"][$key] = serialize($value); + } elseif (isset($_SESSION["HA::CONFIG"][$key])) { + return unserialize($_SESSION["HA::CONFIG"][$key]); + } + + return null; + } + + /** + * Returns value from session storage + * + * @param string $key Key + * @return string|null + */ + public function get($key) { + $key = strtolower($key); + + if (isset($_SESSION["HA::STORE"], $_SESSION["HA::STORE"][$key])) { + return unserialize($_SESSION["HA::STORE"][$key]); + } + + return null; + } + + /** + * Saves a key value pair to the session storage + * + * @param string $key Key + * @param string $value Value + * @return void + */ + public function set($key, $value) { + $key = strtolower($key); + $_SESSION["HA::STORE"][$key] = serialize($value); + } + + /** + * Clear session storage + * @return void + */ + function clear() { + $_SESSION["HA::STORE"] = array(); + } + + /** + * Delete a specific key from session storage + * + * @param string $key Key + * @return void + */ + function delete($key) { + $key = strtolower($key); + + if (isset($_SESSION["HA::STORE"], $_SESSION["HA::STORE"][$key])) { + $f = $_SESSION['HA::STORE']; + unset($f[$key]); + $_SESSION["HA::STORE"] = $f; + } + } + + /** + * Delete all keys recursively from session storage + * + * @param string $key Key + * @retun void + */ + function deleteMatch($key) { + $key = strtolower($key); + + if (isset($_SESSION["HA::STORE"]) && count($_SESSION["HA::STORE"])) { + $f = $_SESSION['HA::STORE']; + foreach ($f as $k => $v) { + if (strstr($k, $key)) { + unset($f[$k]); + } + } + $_SESSION["HA::STORE"] = $f; + } + } + + /** + * Returns session storage as a serialized string + * @return string|null + */ + function getSessionData() { + if (isset($_SESSION["HA::STORE"])) { + return serialize($_SESSION["HA::STORE"]); + } + return null; + } + + /** + * Restores the session from serialized session data + * + * @param string $sessiondata Serialized session data + * @return void + */ + function restoreSessionData($sessiondata = null) { + $_SESSION["HA::STORE"] = unserialize($sessiondata); + } + +} diff --git a/hauth/Hybrid/StorageInterface.php b/hauth/Hybrid/StorageInterface.php new file mode 100644 index 0000000..5b171ec --- /dev/null +++ b/hauth/Hybrid/StorageInterface.php @@ -0,0 +1,29 @@ +timestamp = time(); + $this->profile = new Hybrid_User_Profile(); + } + +} diff --git a/hauth/Hybrid/User_Activity.php b/hauth/Hybrid/User_Activity.php new file mode 100644 index 0000000..4a57e16 --- /dev/null +++ b/hauth/Hybrid/User_Activity.php @@ -0,0 +1,55 @@ +user = new stdClass(); + + // typically, we should have a few information about the user who created the event from social apis + $this->user->identifier = null; + $this->user->displayName = null; + $this->user->profileURL = null; + $this->user->photoURL = null; + } + +} diff --git a/hauth/Hybrid/User_Contact.php b/hauth/Hybrid/User_Contact.php new file mode 100644 index 0000000..facbfc4 --- /dev/null +++ b/hauth/Hybrid/User_Contact.php @@ -0,0 +1,60 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/hauth/Hybrid/resources/index.html b/hauth/Hybrid/resources/index.html new file mode 100644 index 0000000..065d2da --- /dev/null +++ b/hauth/Hybrid/resources/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/hauth/Hybrid/resources/openid_policy.html b/hauth/Hybrid/resources/openid_policy.html new file mode 100644 index 0000000..bf5c52c --- /dev/null +++ b/hauth/Hybrid/resources/openid_policy.html @@ -0,0 +1,10 @@ + + + OpenID Policy + + + + + \ No newline at end of file diff --git a/hauth/Hybrid/resources/openid_realm.html b/hauth/Hybrid/resources/openid_realm.html new file mode 100644 index 0000000..e26a5a1 --- /dev/null +++ b/hauth/Hybrid/resources/openid_realm.html @@ -0,0 +1,13 @@ + + + HybridAuth Endpoint + + + + +

HybridAuth

+ Open Source Social Sign On PHP Library. +
+ hybridauth.sourceforge.net/ + + diff --git a/hauth/Hybrid/resources/openid_xrds.xml b/hauth/Hybrid/resources/openid_xrds.xml new file mode 100644 index 0000000..9d50170 --- /dev/null +++ b/hauth/Hybrid/resources/openid_xrds.xml @@ -0,0 +1,12 @@ + + + + + http://specs.openid.net/auth/2.0/return_to + {RETURN_TO_URL} + + + \ No newline at end of file diff --git a/hauth/Hybrid/thirdparty/Amazon/AmazonOAuth2Client.php b/hauth/Hybrid/thirdparty/Amazon/AmazonOAuth2Client.php new file mode 100644 index 0000000..9c8a363 --- /dev/null +++ b/hauth/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/hauth/Hybrid/thirdparty/OAuth/OAuth.php b/hauth/Hybrid/thirdparty/OAuth/OAuth.php new file mode 100644 index 0000000..e09d77c --- /dev/null +++ b/hauth/Hybrid/thirdparty/OAuth/OAuth.php @@ -0,0 +1,901 @@ +key = $key; + $this->secret = $secret; + $this->callback_url = $callback_url; + } + + function __toString() { + return "OAuthConsumer[key=$this->key,secret=$this->secret]"; + } +} + +class OAuthToken { + // access tokens and request tokens + public $key; + public $secret; + + /** + * key = the token + * secret = the token secret + */ + function __construct($key, $secret) { + $this->key = $key; + $this->secret = $secret; + } + + /** + * generates the basic string serialization of a token that a server + * would respond to request_token and access_token calls with + */ + function to_string() { + return "oauth_token=" . + OAuthUtil::urlencode_rfc3986($this->key) . + "&oauth_token_secret=" . + OAuthUtil::urlencode_rfc3986($this->secret); + } + + function __toString() { + return $this->to_string(); + } +} + +/** + * A class for implementing a Signature Method + * See section 9 ("Signing Requests") in the spec + */ +abstract class OAuthSignatureMethod { + /** + * Needs to return the name of the Signature Method (ie HMAC-SHA1) + * @return string + */ + abstract public function get_name(); + + /** + * Build up the signature + * NOTE: The output of this function MUST NOT be urlencoded. + * the encoding is handled in OAuthRequest when the final + * request is serialized + * @param OAuthRequest $request + * @param OAuthConsumer $consumer + * @param OAuthToken $token + * @return string + */ + abstract public function build_signature($request, $consumer, $token); + + /** + * Verifies that a given signature is correct + * @param OAuthRequest $request + * @param OAuthConsumer $consumer + * @param OAuthToken $token + * @param string $signature + * @return bool + */ + public function check_signature($request, $consumer, $token, $signature) { + $built = $this->build_signature($request, $consumer, $token); + + // Check for zero length, although unlikely here + if (strlen($built) == 0 || strlen($signature) == 0) { + return false; + } + + if (strlen($built) != strlen($signature)) { + return false; + } + + // Avoid a timing leak with a (hopefully) time insensitive compare + $result = 0; + for ($i = 0; $i < strlen($signature); $i++) { + $result |= ord($built{$i}) ^ ord($signature{$i}); + } + + return $result == 0; + } +} + +/** + * The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104] + * where the Signature Base String is the text and the key is the concatenated values (each first + * encoded per Parameter Encoding) of the Consumer Secret and Token Secret, separated by an '&' + * character (ASCII code 38) even if empty. + * - Chapter 9.2 ("HMAC-SHA1") + */ +class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod { + function get_name() { + return "HMAC-SHA1"; + } + + public function build_signature($request, $consumer, $token) { + $base_string = $request->get_signature_base_string(); + $request->base_string = $base_string; + + $key_parts = array( + $consumer->secret, + ($token) ? $token->secret : "" + ); + + $key_parts = OAuthUtil::urlencode_rfc3986($key_parts); + $key = implode('&', $key_parts); + + return base64_encode(hash_hmac('sha1', $base_string, $key, true)); + } +} + +/** + * The PLAINTEXT method does not provide any security protection and SHOULD only be used + * over a secure channel such as HTTPS. It does not use the Signature Base String. + * - Chapter 9.4 ("PLAINTEXT") + */ +class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod { + public function get_name() { + return "PLAINTEXT"; + } + + /** + * oauth_signature is set to the concatenated encoded values of the Consumer Secret and + * Token Secret, separated by a '&' character (ASCII code 38), even if either secret is + * empty. The result MUST be encoded again. + * - Chapter 9.4.1 ("Generating Signatures") + * + * Please note that the second encoding MUST NOT happen in the SignatureMethod, as + * OAuthRequest handles this! + */ + public function build_signature($request, $consumer, $token) { + $key_parts = array( + $consumer->secret, + ($token) ? $token->secret : "" + ); + + $key_parts = OAuthUtil::urlencode_rfc3986($key_parts); + $key = implode('&', $key_parts); + $request->base_string = $key; + + return $key; + } +} + +/** + * The RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature algorithm as defined in + * [RFC3447] section 8.2 (more simply known as PKCS#1), using SHA-1 as the hash function for + * EMSA-PKCS1-v1_5. It is assumed that the Consumer has provided its RSA public key in a + * verified way to the Service Provider, in a manner which is beyond the scope of this + * specification. + * - Chapter 9.3 ("RSA-SHA1") + */ +abstract class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod { + public function get_name() { + return "RSA-SHA1"; + } + + // Up to the SP to implement this lookup of keys. Possible ideas are: + // (1) do a lookup in a table of trusted certs keyed off of consumer + // (2) fetch via http using a url provided by the requester + // (3) some sort of specific discovery code based on request + // + // Either way should return a string representation of the certificate + protected abstract function fetch_public_cert(&$request); + + // Up to the SP to implement this lookup of keys. Possible ideas are: + // (1) do a lookup in a table of trusted certs keyed off of consumer + // + // Either way should return a string representation of the certificate + protected abstract function fetch_private_cert(&$request); + + public function build_signature($request, $consumer, $token) { + $base_string = $request->get_signature_base_string(); + $request->base_string = $base_string; + + // Fetch the private key cert based on the request + $cert = $this->fetch_private_cert($request); + + // Pull the private key ID from the certificate + $privatekeyid = openssl_get_privatekey($cert); + + // Sign using the key + $ok = openssl_sign($base_string, $signature, $privatekeyid); + + // Release the key resource + openssl_free_key($privatekeyid); + + return base64_encode($signature); + } + + public function check_signature($request, $consumer, $token, $signature) { + $decoded_sig = base64_decode($signature); + + $base_string = $request->get_signature_base_string(); + + // Fetch the public key cert based on the request + $cert = $this->fetch_public_cert($request); + + // Pull the public key ID from the certificate + $publickeyid = openssl_get_publickey($cert); + + // Check the computed signature against the one passed in the query + $ok = openssl_verify($base_string, $decoded_sig, $publickeyid); + + // Release the key resource + openssl_free_key($publickeyid); + + return $ok == 1; + } +} + +class OAuthRequest { + protected $parameters; + protected $http_method; + protected $http_url; + // for debug purposes + public $base_string; + public static $version = '1.0'; + public static $POST_INPUT = 'php://input'; + + function __construct($http_method, $http_url, $parameters=null) { + $parameters = ($parameters) ? $parameters : array(); + $parameters = array_merge( OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters); + $this->parameters = $parameters; + $this->http_method = $http_method; + $this->http_url = $http_url; + } + + + /** + * attempt to build up a request from what was passed to the server + */ + public static function from_request($http_method=null, $http_url=null, $parameters=null) { + $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on") + ? 'http' + : 'https'; + if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) { + $scheme = $_SERVER['HTTP_X_FORWARDED_PROTO']; + } + $http_url = ($http_url) ? $http_url : $scheme . + '://' . $_SERVER['SERVER_NAME'] . + ':' . + $_SERVER['SERVER_PORT'] . + $_SERVER['REQUEST_URI']; + $http_method = ($http_method) ? $http_method : $_SERVER['REQUEST_METHOD']; + + // We weren't handed any parameters, so let's find the ones relevant to + // this request. + // If you run XML-RPC or similar you should use this to provide your own + // parsed parameter-list + if (!$parameters) { + // Find request headers + $request_headers = OAuthUtil::get_headers(); + + // Parse the query-string to find GET parameters + $parameters = OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']); + + // It's a POST request of the proper content-type, so parse POST + // parameters and add those overriding any duplicates from GET + if ($http_method == "POST" + && isset($request_headers['Content-Type']) + && strstr($request_headers['Content-Type'], + 'application/x-www-form-urlencoded') + ) { + $post_data = OAuthUtil::parse_parameters( + file_get_contents(self::$POST_INPUT) + ); + $parameters = array_merge($parameters, $post_data); + } + + // We have a Authorization-header with OAuth data. Parse the header + // and add those overriding any duplicates from GET or POST + if (isset($request_headers['Authorization']) && substr($request_headers['Authorization'], 0, 6) == 'OAuth ') { + $header_parameters = OAuthUtil::split_header( + $request_headers['Authorization'] + ); + $parameters = array_merge($parameters, $header_parameters); + } + + } + + return new OAuthRequest($http_method, $http_url, $parameters); + } + + /** + * pretty much a helper function to set up the request + */ + public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=null) { + $parameters = ($parameters) ? $parameters : array(); + $defaults = array("oauth_version" => OAuthRequest::$version, + "oauth_nonce" => OAuthRequest::generate_nonce(), + "oauth_timestamp" => OAuthRequest::generate_timestamp(), + "oauth_consumer_key" => $consumer->key); + if ($token) + $defaults['oauth_token'] = $token->key; + + $parameters = array_merge($defaults, $parameters); + + return new OAuthRequest($http_method, $http_url, $parameters); + } + + public function set_parameter($name, $value, $allow_duplicates = true) { + if ($allow_duplicates && isset($this->parameters[$name])) { + // We have already added parameter(s) with this name, so add to the list + if (is_scalar($this->parameters[$name])) { + // This is the first duplicate, so transform scalar (string) + // into an array so we can add the duplicates + $this->parameters[$name] = array($this->parameters[$name]); + } + + $this->parameters[$name][] = $value; + } else { + $this->parameters[$name] = $value; + } + } + + public function get_parameter($name) { + return isset($this->parameters[$name]) ? $this->parameters[$name] : null; + } + + public function get_parameters() { + return $this->parameters; + } + + public function unset_parameter($name) { + unset($this->parameters[$name]); + } + + /** + * The request parameters, sorted and concatenated into a normalized string. + * @return string + */ + public function get_signable_parameters() { + // Grab all parameters + $params = $this->parameters; + + // Remove oauth_signature if present + // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.") + if (isset($params['oauth_signature'])) { + unset($params['oauth_signature']); + } + + return OAuthUtil::build_http_query($params); + } + + /** + * Returns the base string of this request + * + * The base string defined as the method, the url + * and the parameters (normalized), each urlencoded + * and the concated with &. + */ + public function get_signature_base_string() { + $parts = array( + $this->get_normalized_http_method(), + $this->get_normalized_http_url(), + $this->get_signable_parameters() + ); + + $parts = OAuthUtil::urlencode_rfc3986($parts); + + return implode('&', $parts); + } + + /** + * just uppercases the http method + */ + public function get_normalized_http_method() { + return strtoupper($this->http_method); + } + + /** + * parses the url and rebuilds it to be + * scheme://host/path + */ + public function get_normalized_http_url() { + $parts = parse_url($this->http_url); + + $scheme = (isset($parts['scheme'])) ? $parts['scheme'] : 'http'; + $port = (isset($parts['port'])) ? $parts['port'] : (($scheme == 'https') ? '443' : '80'); + $host = (isset($parts['host'])) ? strtolower($parts['host']) : ''; + $path = (isset($parts['path'])) ? $parts['path'] : ''; + + if (($scheme == 'https' && $port != '443') + || ($scheme == 'http' && $port != '80')) { + $host = "$host:$port"; + } + return "$scheme://$host$path"; + } + + /** + * builds a url usable for a GET request + */ + public function to_url() { + $post_data = $this->to_postdata(); + $out = $this->get_normalized_http_url(); + if ($post_data) { + $out .= '?'.$post_data; + } + return $out; + } + + /** + * builds the data one would send in a POST request + */ + public function to_postdata() { + return OAuthUtil::build_http_query($this->parameters); + } + + /** + * builds the Authorization: header + */ + public function to_header($realm=null) { + $first = true; + if($realm) { + $out = 'Authorization: OAuth realm="' . OAuthUtil::urlencode_rfc3986($realm) . '"'; + $first = false; + } else + $out = 'Authorization: OAuth'; + + $total = array(); + foreach ($this->parameters as $k => $v) { + if (substr($k, 0, 5) != "oauth") continue; + if (is_array($v)) { + throw new OAuthException('arrays not supported in headers'); + } + $out .= ($first) ? ' ' : ','; + $out .= OAuthUtil::urlencode_rfc3986($k) . + '="' . + OAuthUtil::urlencode_rfc3986($v) . + '"'; + $first = false; + } + return $out; + } + + public function __toString() { + return $this->to_url(); + } + + + public function sign_request($signature_method, $consumer, $token) { + $this->set_parameter( + "oauth_signature_method", + $signature_method->get_name(), + false + ); + $signature = $this->build_signature($signature_method, $consumer, $token); + $this->set_parameter("oauth_signature", $signature, false); + } + + public function build_signature($signature_method, $consumer, $token) { + $signature = $signature_method->build_signature($this, $consumer, $token); + return $signature; + } + + /** + * util function: current timestamp + */ + private static function generate_timestamp() { + return time(); + } + + /** + * util function: current nonce + */ + private static function generate_nonce() { + $mt = microtime(); + $rand = mt_rand(); + + return md5($mt . $rand); // md5s look nicer than numbers + } +} + +class OAuthServer { + protected $timestamp_threshold = 300; // in seconds, five minutes + protected $version = '1.0'; // hi blaine + protected $signature_methods = array(); + + protected $data_store; + + function __construct($data_store) { + $this->data_store = $data_store; + } + + public function add_signature_method($signature_method) { + $this->signature_methods[$signature_method->get_name()] = + $signature_method; + } + + // high level functions + + /** + * process a request_token request + * returns the request token on success + */ + public function fetch_request_token(&$request) { + $this->get_version($request); + + $consumer = $this->get_consumer($request); + + // no token required for the initial token request + $token = null; + + $this->check_signature($request, $consumer, $token); + + // Rev A change + $callback = $request->get_parameter('oauth_callback'); + $new_token = $this->data_store->new_request_token($consumer, $callback); + + return $new_token; + } + + /** + * process an access_token request + * returns the access token on success + */ + public function fetch_access_token(&$request) { + $this->get_version($request); + + $consumer = $this->get_consumer($request); + + // requires authorized request token + $token = $this->get_token($request, $consumer, "request"); + + $this->check_signature($request, $consumer, $token); + + // Rev A change + $verifier = $request->get_parameter('oauth_verifier'); + $new_token = $this->data_store->new_access_token($token, $consumer, $verifier); + + return $new_token; + } + + /** + * verify an api call, checks all the parameters + */ + public function verify_request(&$request) { + $this->get_version($request); + $consumer = $this->get_consumer($request); + $token = $this->get_token($request, $consumer, "access"); + $this->check_signature($request, $consumer, $token); + return array($consumer, $token); + } + + // Internals from here + /** + * version 1 + */ + private function get_version(&$request) { + $version = $request->get_parameter("oauth_version"); + if (!$version) { + // Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present. + // Chapter 7.0 ("Accessing Protected Ressources") + $version = '1.0'; + } + if ($version !== $this->version) { + throw new OAuthException("OAuth version '$version' not supported"); + } + return $version; + } + + /** + * figure out the signature with some defaults + */ + private function get_signature_method($request) { + $signature_method = $request instanceof OAuthRequest + ? $request->get_parameter("oauth_signature_method") + : null; + + if (!$signature_method) { + // According to chapter 7 ("Accessing Protected Ressources") the signature-method + // parameter is required, and we can't just fallback to PLAINTEXT + throw new OAuthException('No signature method parameter. This parameter is required'); + } + + if (!in_array($signature_method, + array_keys($this->signature_methods))) { + throw new OAuthException( + "Signature method '$signature_method' not supported " . + "try one of the following: " . + implode(", ", array_keys($this->signature_methods)) + ); + } + return $this->signature_methods[$signature_method]; + } + + /** + * try to find the consumer for the provided request's consumer key + */ + private function get_consumer($request) { + $consumer_key = $request instanceof OAuthRequest + ? $request->get_parameter("oauth_consumer_key") + : null; + + if (!$consumer_key) { + throw new OAuthException("Invalid consumer key"); + } + + $consumer = $this->data_store->lookup_consumer($consumer_key); + if (!$consumer) { + throw new OAuthException("Invalid consumer"); + } + + return $consumer; + } + + /** + * try to find the token for the provided request's token key + */ + private function get_token($request, $consumer, $token_type="access") { + $token_field = $request instanceof OAuthRequest + ? $request->get_parameter('oauth_token') + : null; + + $token = $this->data_store->lookup_token( + $consumer, $token_type, $token_field + ); + if (!$token) { + throw new OAuthException("Invalid $token_type token: $token_field"); + } + return $token; + } + + /** + * all-in-one function to check the signature on a request + * should guess the signature method appropriately + */ + private function check_signature($request, $consumer, $token) { + // this should probably be in a different method + $timestamp = $request instanceof OAuthRequest + ? $request->get_parameter('oauth_timestamp') + : null; + $nonce = $request instanceof OAuthRequest + ? $request->get_parameter('oauth_nonce') + : null; + + $this->check_timestamp($timestamp); + $this->check_nonce($consumer, $token, $nonce, $timestamp); + + $signature_method = $this->get_signature_method($request); + + $signature = $request->get_parameter('oauth_signature'); + $valid_sig = $signature_method->check_signature( + $request, + $consumer, + $token, + $signature + ); + + if (!$valid_sig) { + throw new OAuthException("Invalid signature"); + } + } + + /** + * check that the timestamp is new enough + */ + private function check_timestamp($timestamp) { + if( ! $timestamp ) + throw new OAuthException( + 'Missing timestamp parameter. The parameter is required' + ); + + // verify that timestamp is recentish + $now = time(); + if (abs($now - $timestamp) > $this->timestamp_threshold) { + throw new OAuthException( + "Expired timestamp, yours $timestamp, ours $now" + ); + } + } + + /** + * check that the nonce is not repeated + */ + private function check_nonce($consumer, $token, $nonce, $timestamp) { + if( ! $nonce ) + throw new OAuthException( + 'Missing nonce parameter. The parameter is required' + ); + + // verify that the nonce is uniqueish + $found = $this->data_store->lookup_nonce( + $consumer, + $token, + $nonce, + $timestamp + ); + if ($found) { + throw new OAuthException("Nonce already used: $nonce"); + } + } + +} + +class OAuthDataStore { + function lookup_consumer($consumer_key) { + // implement me + } + + function lookup_token($consumer, $token_type, $token) { + // implement me + } + + function lookup_nonce($consumer, $token, $nonce, $timestamp) { + // implement me + } + + function new_request_token($consumer, $callback = null) { + // return a new token attached to this consumer + } + + function new_access_token($token, $consumer, $verifier = null) { + // return a new access token attached to this consumer + // for the user associated with this token if the request token + // is authorized + // should also invalidate the request token + } + +} + +class OAuthUtil { + public static function urlencode_rfc3986($input) { + if (is_array($input)) { + return array_map(array('OAuthUtil', 'urlencode_rfc3986'), $input); + } else if (is_scalar($input)) { + return str_replace( + '+', + ' ', + str_replace('%7E', '~', rawurlencode($input)) + ); + } else { + return ''; + } +} + + + // This decode function isn't taking into consideration the above + // modifications to the encoding process. However, this method doesn't + // seem to be used anywhere so leaving it as is. + public static function urldecode_rfc3986($string) { + return urldecode($string); + } + + // Utility function for turning the Authorization: header into + // parameters, has to do some unescaping + // Can filter out any non-oauth parameters if needed (default behaviour) + // May 28th, 2010 - method updated to tjerk.meesters for a speed improvement. + // see http://code.google.com/p/oauth/issues/detail?id=163 + public static function split_header($header, $only_allow_oauth_parameters = true) { + $params = array(); + if (preg_match_all('/('.($only_allow_oauth_parameters ? 'oauth_' : '').'[a-z_-]*)=(:?"([^"]*)"|([^,]*))/', $header, $matches)) { + foreach ($matches[1] as $i => $h) { + $params[$h] = OAuthUtil::urldecode_rfc3986(empty($matches[3][$i]) ? $matches[4][$i] : $matches[3][$i]); + } + if (isset($params['realm'])) { + unset($params['realm']); + } + } + return $params; + } + + // helper to try to sort out headers for people who aren't running apache + public static function get_headers() { + if (function_exists('apache_request_headers')) { + // we need this to get the actual Authorization: header + // because apache tends to tell us it doesn't exist + $headers = apache_request_headers(); + + // sanitize the output of apache_request_headers because + // we always want the keys to be Cased-Like-This and arh() + // returns the headers in the same case as they are in the + // request + $out = array(); + foreach ($headers AS $key => $value) { + $key = str_replace( + " ", + "-", + ucwords(strtolower(str_replace("-", " ", $key))) + ); + $out[$key] = $value; + } + } else { + // otherwise we don't have apache and are just going to have to hope + // that $_SERVER actually contains what we need + $out = array(); + if( isset($_SERVER['CONTENT_TYPE']) ) + $out['Content-Type'] = $_SERVER['CONTENT_TYPE']; + if( isset($_ENV['CONTENT_TYPE']) ) + $out['Content-Type'] = $_ENV['CONTENT_TYPE']; + + foreach ($_SERVER as $key => $value) { + if (substr($key, 0, 5) == "HTTP_") { + // this is chaos, basically it is just there to capitalize the first + // letter of every word that is not an initial HTTP and strip HTTP + // code from przemek + $key = str_replace( + " ", + "-", + ucwords(strtolower(str_replace("_", " ", substr($key, 5)))) + ); + $out[$key] = $value; + } + } + } + return $out; + } + + // This function takes a input like a=b&a=c&d=e and returns the parsed + // parameters like this + // array('a' => array('b','c'), 'd' => 'e') + public static function parse_parameters( $input ) { + if (!isset($input) || !$input) return array(); + + $pairs = explode('&', $input); + + $parsed_parameters = array(); + foreach ($pairs as $pair) { + $split = explode('=', $pair, 2); + $parameter = OAuthUtil::urldecode_rfc3986($split[0]); + $value = isset($split[1]) ? OAuthUtil::urldecode_rfc3986($split[1]) : ''; + + if (isset($parsed_parameters[$parameter])) { + // We have already recieved parameter(s) with this name, so add to the list + // of parameters with this name + + if (is_scalar($parsed_parameters[$parameter])) { + // This is the first duplicate, so transform scalar (string) into an array + // so we can add the duplicates + $parsed_parameters[$parameter] = array($parsed_parameters[$parameter]); + } + + $parsed_parameters[$parameter][] = $value; + } else { + $parsed_parameters[$parameter] = $value; + } + } + return $parsed_parameters; + } + + public static function build_http_query($params) { + if (!$params) return ''; + + // Urlencode both keys and values + $keys = OAuthUtil::urlencode_rfc3986(array_keys($params)); + $values = OAuthUtil::urlencode_rfc3986(array_values($params)); + $params = array_combine($keys, $values); + + // Parameters are sorted by name, using lexicographical byte value ordering. + // Ref: Spec: 9.1.1 (1) + uksort($params, 'strcmp'); + + $pairs = array(); + foreach ($params as $parameter => $value) { + if (is_array($value)) { + // If two or more parameters share the same name, they are sorted by their value + // Ref: Spec: 9.1.1 (1) + // June 12th, 2010 - changed to sort because of issue 164 by hidetaka + sort($value, SORT_STRING); + foreach ($value as $duplicate_value) { + $pairs[] = $parameter . '=' . $duplicate_value; + } + } else { + $pairs[] = $parameter . '=' . $value; + } + } + // For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61) + // Each name-value pair is separated by an '&' character (ASCII code 38) + return implode('&', $pairs); + } +} diff --git a/hauth/Hybrid/thirdparty/OAuth/OAuth1Client.php b/hauth/Hybrid/thirdparty/OAuth/OAuth1Client.php new file mode 100644 index 0000000..64c03c8 --- /dev/null +++ b/hauth/Hybrid/thirdparty/OAuth/OAuth1Client.php @@ -0,0 +1,264 @@ +sha1_method = new OAuthSignatureMethod_HMAC_SHA1(); + $this->consumer = new OAuthConsumer( $consumer_key, $consumer_secret ); + $this->token = null; + + if ( $oauth_token && $oauth_token_secret ){ + $this->token = new OAuthConsumer( $oauth_token, $oauth_token_secret ); + } + } + + /** + * Build authorize url + * + * @return string + */ + function authorizeUrl( $token, $extras =array() ) + { + if ( is_array( $token ) ){ + $token = $token['oauth_token']; + } + + $parameters = array( "oauth_token" => $token ); + + if( count($extras) ) + foreach( $extras as $k=>$v ) + $parameters[$k] = $v; + + return $this->authorize_url . "?" . http_build_query( $parameters ); + } + + /** + * Get a request_token from provider + * + * @return array a key/value array containing oauth_token and oauth_token_secret + */ + function requestToken( $callback = null ) + { + $parameters = array(); + + if ( $callback ) { + $this->redirect_uri = $parameters['oauth_callback'] = $callback; + } + + $request = $this->signedRequest( $this->request_token_url, $this->request_token_method, $parameters ); + $token = OAuthUtil::parse_parameters( $request ); + $this->token = new OAuthConsumer( $token['oauth_token'], $token['oauth_token_secret'] ); + + return $token; + } + + /** + * Exchange the request token and secret for an access token and secret, to sign API calls. + * + * @return array array('oauth_token' => the access token, 'oauth_token_secret' => the access secret) + */ + function accessToken( $oauth_verifier = false, $oauth_token = false ) + { + $parameters = array(); + + // 1.0a + if ( $oauth_verifier ) { + $parameters['oauth_verifier'] = $oauth_verifier; + } + + $request = $this->signedRequest( $this->access_token_url, $this->access_token_method, $parameters ); + $token = OAuthUtil::parse_parameters( $request ); + $this->token = new OAuthConsumer( $token['oauth_token'], $token['oauth_token_secret'] ); + + return $token; + } + + /** + * GET wrapper for provider apis request + */ + function get($url, $parameters = array(), $content_type = null) + { + return $this->api($url, 'GET', $parameters, null, $content_type); + } + + /** + * POST wrapper for provider apis request + */ + function post($url, $parameters = array(), $body = null, $content_type = null, $multipart = false) + { + return $this->api($url, 'POST', $parameters, $body, $content_type, $multipart ); + } + + /** + * Format and sign an oauth for provider api + */ + function api( $url, $method = 'GET', $parameters = array(), $body = null, $content_type = null, $multipart = false ) + { + if ( strrpos($url, 'http://') !== 0 && strrpos($url, 'https://') !== 0 ) { + $url = $this->api_base_url . $url; + } + + $response = $this->signedRequest( $url, $method, $parameters, $body, $content_type, $multipart ); + + if( $this->decode_json ){ + $response = json_decode( $response ); + } + + return $this->response = $response; + } + + /** + * Return the response object afer the fact + * + * @return mixed + */ + public function getResponse() + { + return $this->response; + } + + /** + * Make signed request + */ + function signedRequest( $url, $method, $parameters, $body = null, $content_type = null, $multipart = false ) + { + + $signature_parameters = array(); + + // when making a multipart request, use only oauth_* keys for signature + foreach( $parameters AS $key => $value ){ + if( !$multipart || strpos( $key, 'oauth_' ) === 0 ){ + $signature_parameters[$key] = $value; + } + } + + $request = OAuthRequest::from_consumer_and_token($this->consumer, $this->token, $method, $url, $signature_parameters); + $request->sign_request($this->sha1_method, $this->consumer, $this->token); + switch ($method) { + case 'GET': return $this->request( $request->to_url(), 'GET', null, null, $content_type ); + default : + if ($body) + return $this->request( $request->to_url(), $method, $body, $request->to_header(), $content_type ); + else + return $this->request( $request->get_normalized_http_url(), $method, ($multipart ? $parameters : $request->to_postdata()), $request->to_header(), $content_type, $multipart ) ; + } + } + + /** + * Make http request + */ + function request( $url, $method, $postfields = null, $auth_header = null, $content_type = null, $multipart = false ) + { + Hybrid_Logger::info( "Enter OAuth1Client::request( $method, $url )" ); + Hybrid_Logger::debug( "OAuth1Client::request(). dump post fields: ", serialize( $postfields ) ); + + $this->http_info = array(); + $ci = curl_init(); + + /* Curl settings */ + curl_setopt( $ci, CURLOPT_USERAGENT , $this->curl_useragent ); + curl_setopt( $ci, CURLOPT_CONNECTTIMEOUT, $this->curl_connect_time_out ); + curl_setopt( $ci, CURLOPT_TIMEOUT , $this->curl_time_out ); + curl_setopt( $ci, CURLOPT_RETURNTRANSFER, true ); + curl_setopt( $ci, CURLOPT_HTTPHEADER , array('Expect:') ); + curl_setopt( $ci, CURLOPT_SSL_VERIFYPEER, $this->curl_ssl_verifypeer ); + curl_setopt( $ci, CURLOPT_HEADERFUNCTION, array($this, 'getHeader') ); + curl_setopt( $ci, CURLOPT_HEADER , false ); + + if( $multipart ){ + curl_setopt( $ci, CURLOPT_HTTPHEADER, array( 'Expect:', $auth_header ) ); + + }elseif ($content_type) + curl_setopt( $ci, CURLOPT_HTTPHEADER, array('Expect:', "Content-Type: $content_type") ); + + if($this->curl_proxy){ + curl_setopt( $ci, CURLOPT_PROXY , $this->curl_proxy); + } + + switch ($method){ + case 'POST': + curl_setopt( $ci, CURLOPT_POST, true ); + + if ( !empty($postfields) ){ + curl_setopt( $ci, CURLOPT_POSTFIELDS, $postfields ); + } + + if ( !empty($auth_header) && $this->curl_auth_header && !$multipart ){ + curl_setopt( $ci, CURLOPT_HTTPHEADER, array( 'Content-Type: application/atom+xml', $auth_header ) ); + } + break; + case 'DELETE': + curl_setopt( $ci, CURLOPT_CUSTOMREQUEST, 'DELETE' ); + if ( !empty($postfields) ){ + $url = "{$url}?{$postfields}"; + } + } + + curl_setopt($ci, CURLOPT_URL, $url); + $response = curl_exec($ci); + if( $response === false ) { + Hybrid_Logger::error( "OAuth1Client::request(). curl_exec error: ", curl_error($ci) ); + } + + + Hybrid_Logger::debug( "OAuth1Client::request(). dump request info: ", serialize( curl_getinfo($ci) ) ); + Hybrid_Logger::debug( "OAuth1Client::request(). dump request result: ", serialize( $response ) ); + + $this->http_code = curl_getinfo($ci, CURLINFO_HTTP_CODE); + $this->http_info = array_merge($this->http_info, curl_getinfo($ci)); + + curl_close ($ci); + + return $response; + } + + /** + * Get the header info to store. + */ + function getHeader($ch, $header) { + $i = strpos($header, ':'); + + if ( !empty($i) ){ + $key = str_replace('-', '_', strtolower(substr($header, 0, $i))); + $value = trim(substr($header, $i + 2)); + $this->http_header[$key] = $value; + } + + return strlen($header); + } +} diff --git a/hauth/Hybrid/thirdparty/OAuth/OAuth2Client.php b/hauth/Hybrid/thirdparty/OAuth/OAuth2Client.php new file mode 100644 index 0000000..0046d2c --- /dev/null +++ b/hauth/Hybrid/thirdparty/OAuth/OAuth2Client.php @@ -0,0 +1,302 @@ +client_id = $client_id; + $this->client_secret = $client_secret; + $this->redirect_uri = $redirect_uri; + $this->curl_compressed = $compressed; + } + + public function authorizeUrl( $extras = array() ) + { + $params = array( + "client_id" => $this->client_id, + "redirect_uri" => $this->redirect_uri, + "response_type" => "code" + ); + + if( count($extras) ) + foreach( $extras as $k=>$v ) + $params[$k] = $v; + + return $this->authorize_url . "?" . http_build_query($params, '', '&'); + } + + public function authenticate( $code ) + { + $params = array( + "client_id" => $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, $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; + } + + public function authenticated() + { + if ( $this->access_token ){ + if ( $this->token_info_url && $this->refresh_token ){ + // check if this access token has expired, + $tokeninfo = $this->tokenInfo( $this->access_token ); + + // if yes, access_token has expired, then ask for a new one + if( $tokeninfo && isset( $tokeninfo->error ) ){ + $response = $this->refreshToken( $this->refresh_token ); + + // if wrong response + if( ! isset( $response->access_token ) || ! $response->access_token ){ + throw new Exception( "The Authorization Service has return an invalid response while requesting a new access token. given up!" ); + } + + // set new access_token + $this->access_token = $response->access_token; + } + } + + return true; + } + + return false; + } + + /** + * Format and sign an oauth for provider api + */ + 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; + } + + $parameters[$this->sign_token_name] = $this->access_token; + $response = null; + + 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 && $decode_json ){ + return $this->response = json_decode( $response ); + } + + return $this->response = $response; + } + + /** + * Return the response object afer the fact + * + * @return mixed + */ + public function getResponse() + { + return $this->response; + } + + /** + * GET wrapper for provider apis request + */ + function get( $url, $parameters = array(), $decode_json = true ) + { + return $this->api( $url, 'GET', $parameters, $decode_json ); + } + + /** + * POST wrapper for provider apis request + */ + function post( $url, $parameters = array(), $decode_json = true ) + { + return $this->api( $url, 'POST', $parameters, $decode_json ); + } + + // -- tokens + + public function tokenInfo($accesstoken) + { + $params['access_token'] = $this->access_token; + $response = $this->request( $this->token_info_url, $params ); + return $this->parseRequestResult( $response ); + } + + public function refreshToken( $parameters = array() ) + { + $params = array( + "client_id" => $this->client_id, + "client_secret" => $this->client_secret, + "grant_type" => "refresh_token" + ); + + foreach($parameters as $k=>$v ){ + $params[$k] = $v; + } + + $response = $this->request( $this->token_url, $params, "POST" ); + return $this->parseRequestResult( $response ); + } + + // -- utilities + + 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 ) ); + + $urlEncodedParams = http_build_query($params, '', '&'); + + if( $type == "GET" ){ + $url = $url . ( strpos( $url, '?' ) ? '&' : '?' ) . $urlEncodedParams; + } + + $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 request body exists then encode it for "application/json". + if (isset($params['body'])) { + $urlEncodedParams = json_encode($params['body']); + } + + // Using URL encoded params here instead of a more convenient array + // cURL will set a wrong HTTP Content-Type header if using an array (cf. http://www.php.net/manual/en/function.curl-setopt.php, Notes section for "CURLOPT_POSTFIELDS") + // OAuth requires application/x-www-form-urlencoded Content-Type (cf. https://tools.ietf.org/html/rfc6749#section-2.3.1) + if ($params) { + curl_setopt($ch, CURLOPT_POSTFIELDS, $urlEncodedParams); + } + } + + 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; + } + /** + * 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/hauth/Hybrid/thirdparty/OpenID/LightOpenID.php b/hauth/Hybrid/thirdparty/OpenID/LightOpenID.php new file mode 100644 index 0000000..a257d6c --- /dev/null +++ b/hauth/Hybrid/thirdparty/OpenID/LightOpenID.php @@ -0,0 +1,1051 @@ += 5.1.2 with cURL or HTTP/HTTPS stream wrappers enabled. + * + * @version v1.2.0 (2014-01-14) + * @link https://code.google.com/p/lightopenid/ Project URL + * @link https://github.com/iignatov/LightOpenID GitHub Repo + * @author Mewp + * @copyright Copyright (c) 2013 Mewp + * @license http://opensource.org/licenses/mit-license.php MIT License + */ +class LightOpenID +{ + public $returnUrl + , $required = array() + , $optional = array() + , $verify_peer = null + , $capath = null + , $cainfo = null + , $cnmatch = null + , $data + , $oauth = array() + , $curl_time_out = 30 + , $curl_connect_time_out = 30; + private $identity, $claimed_id; + protected $server, $version, $trustRoot, $aliases, $identifier_select = false + , $ax = false, $sreg = false, $setup_url = null, $headers = array() + , $proxy = null, $user_agent = 'LightOpenID' + , $xrds_override_pattern = null, $xrds_override_replacement = null; + static protected $ax_to_sreg = array( + 'namePerson/friendly' => 'nickname', + 'contact/email' => 'email', + 'namePerson' => 'fullname', + 'birthDate' => 'dob', + 'person/gender' => 'gender', + 'contact/postalCode/home' => 'postcode', + 'contact/country/home' => 'country', + 'pref/language' => 'language', + 'pref/timezone' => 'timezone', + ); + + function __construct($host, $proxy = null) + { + $this->set_realm($host); + $this->set_proxy($proxy); + + $uri = rtrim(preg_replace('#((?<=\?)|&)openid\.[^&]+#', '', $_SERVER['REQUEST_URI']), '?'); + $this->returnUrl = $this->trustRoot . $uri; + + $this->data = ($_SERVER['REQUEST_METHOD'] === 'POST') ? $_POST : $_GET; + + if(!function_exists('curl_init') && !in_array('https', stream_get_wrappers())) { + throw new ErrorException('You must have either https wrappers or curl enabled.'); + } + } + + function __isset($name) + { + return in_array($name, array('identity', 'trustRoot', 'realm', 'xrdsOverride', 'mode')); + } + + function __set($name, $value) + { + switch ($name) { + case 'identity': + if (strlen($value = trim((String) $value))) { + if (preg_match('#^xri:/*#i', $value, $m)) { + $value = substr($value, strlen($m[0])); + } elseif (!preg_match('/^(?:[=@+\$!\(]|https?:)/i', $value)) { + $value = "http://$value"; + } + if (preg_match('#^https?://[^/]+$#i', $value, $m)) { + $value .= '/'; + } + } + $this->$name = $this->claimed_id = $value; + break; + case 'trustRoot': + case 'realm': + $this->trustRoot = trim($value); + break; + case 'xrdsOverride': + if (is_array($value)) { + list($pattern, $replacement) = $value; + $this->xrds_override_pattern = $pattern; + $this->xrds_override_replacement = $replacement; + } else { + trigger_error('Invalid value specified for "xrdsOverride".', E_USER_ERROR); + } + break; + } + } + + function __get($name) + { + switch ($name) { + case 'identity': + # We return claimed_id instead of identity, + # because the developer should see the claimed identifier, + # i.e. what he set as identity, not the op-local identifier (which is what we verify) + return $this->claimed_id; + case 'trustRoot': + case 'realm': + return $this->trustRoot; + case 'mode': + return empty($this->data['openid_mode']) ? null : $this->data['openid_mode']; + } + } + + function set_proxy($proxy) + { + if (!empty($proxy)) { + // When the proxy is a string - try to parse it. + if (!is_array($proxy)) { + $proxy = parse_url($proxy); + } + + // Check if $proxy is valid after the parsing. + if ($proxy && !empty($proxy['host'])) { + // Make sure that a valid port number is specified. + if (array_key_exists('port', $proxy)) { + if (!is_int($proxy['port'])) { + $proxy['port'] = is_numeric($proxy['port']) ? intval($proxy['port']) : 0; + } + + if ($proxy['port'] <= 0) { + throw new ErrorException('The specified proxy port number is invalid.'); + } + } + + $this->proxy = $proxy; + } + } + } + + /** + * Checks if the server specified in the url exists. + * + * @param $url url to check + * @return true, if the server exists; false otherwise + */ + function hostExists($url) + { + if (strpos($url, '/') === false) { + $server = $url; + } else { + $server = @parse_url($url, PHP_URL_HOST); + } + + if (!$server) { + return false; + } + + return !!gethostbynamel($server); + } + + protected function set_realm($uri) + { + $realm = ''; + + # Set a protocol, if not specified. + $realm .= (($offset = strpos($uri, '://')) === false) ? $this->get_realm_protocol() : ''; + + # Set the offset properly. + $offset = (($offset !== false) ? $offset + 3 : 0); + + # Get only the root, without the path. + $realm .= (($end = strpos($uri, '/', $offset)) === false) ? $uri : substr($uri, 0, $end); + + $this->trustRoot = $realm; + } + + protected function get_realm_protocol() + { + if (!empty($_SERVER['HTTPS'])) { + $use_secure_protocol = ($_SERVER['HTTPS'] != 'off'); + } else if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) { + $use_secure_protocol = ($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https'); + } else { + $use_secure_protocol = false; + } + + return $use_secure_protocol ? 'https://' : 'http://'; + } + + protected function request_curl($url, $method='GET', $params=array(), $update_claimed_id) + { + $params = http_build_query($params, '', '&'); + $curl = curl_init($url . ($method == 'GET' && $params ? '?' . $params : '')); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($curl, CURLOPT_HEADER, false); + curl_setopt($curl, CURLOPT_USERAGENT, $this->user_agent); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_TIMEOUT, $this->curl_time_out); + curl_setopt($curl, CURLOPT_CONNECTTIMEOUT , $this->curl_connect_time_out); + + + if ($method == 'POST') { + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-type: application/x-www-form-urlencoded')); + } else { + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Accept: application/xrds+xml, */*')); + } + + if (!empty($this->proxy)) { + curl_setopt($curl, CURLOPT_PROXY, $this->proxy['host']); + + if (!empty($this->proxy['port'])) { + curl_setopt($curl, CURLOPT_PROXYPORT, $this->proxy['port']); + } + + if (!empty($this->proxy['user'])) { + curl_setopt($curl, CURLOPT_PROXYUSERPWD, $this->proxy['user'] . ':' . $this->proxy['pass']); + } + } + + if($this->verify_peer !== null) { + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verify_peer); + if($this->capath) { + curl_setopt($curl, CURLOPT_CAPATH, $this->capath); + } + + if($this->cainfo) { + curl_setopt($curl, CURLOPT_CAINFO, $this->cainfo); + } + } + + if ($method == 'POST') { + curl_setopt($curl, CURLOPT_POST, true); + curl_setopt($curl, CURLOPT_POSTFIELDS, $params); + } elseif ($method == 'HEAD') { + curl_setopt($curl, CURLOPT_HEADER, true); + curl_setopt($curl, CURLOPT_NOBODY, true); + } else { + curl_setopt($curl, CURLOPT_HEADER, true); + curl_setopt($curl, CURLOPT_HTTPGET, true); + } + $response = curl_exec($curl); + + if($method == 'HEAD' && curl_getinfo($curl, CURLINFO_HTTP_CODE) == 405) { + curl_setopt($curl, CURLOPT_HTTPGET, true); + $response = curl_exec($curl); + $response = substr($response, 0, strpos($response, "\r\n\r\n")); + } + + if($method == 'HEAD' || $method == 'GET') { + $header_response = $response; + + # If it's a GET request, we want to only parse the header part. + if($method == 'GET') { + $header_response = substr($response, 0, strpos($response, "\r\n\r\n")); + } + + $headers = array(); + foreach(explode("\n", $header_response) as $header) { + $pos = strpos($header,':'); + if ($pos !== false) { + $name = strtolower(trim(substr($header, 0, $pos))); + $headers[$name] = trim(substr($header, $pos+1)); + } + } + + if($update_claimed_id) { + # Update the claimed_id value in case of redirections. + $effective_url = curl_getinfo($curl, CURLINFO_EFFECTIVE_URL); + # Ignore the fragment (some cURL versions don't handle it well). + if (strtok($effective_url, '#') != strtok($url, '#')) { + $this->identity = $this->claimed_id = $effective_url; + } + } + + if($method == 'HEAD') { + return $headers; + } else { + $this->headers = $headers; + } + } + + if (curl_errno($curl)) { + throw new ErrorException(curl_error($curl), curl_errno($curl)); + } + + return $response; + } + + protected function parse_header_array($array, $update_claimed_id) + { + $headers = array(); + foreach($array as $header) { + $pos = strpos($header,':'); + if ($pos !== false) { + $name = strtolower(trim(substr($header, 0, $pos))); + $headers[$name] = trim(substr($header, $pos+1)); + + # Following possible redirections. The point is just to have + # claimed_id change with them, because the redirections + # are followed automatically. + # We ignore redirections with relative paths. + # If any known provider uses them, file a bug report. + if($name == 'location' && $update_claimed_id) { + if(strpos($headers[$name], 'http') === 0) { + $this->identity = $this->claimed_id = $headers[$name]; + } elseif($headers[$name][0] == '/') { + $parsed_url = parse_url($this->claimed_id); + $this->identity = + $this->claimed_id = $parsed_url['scheme'] . '://' + . $parsed_url['host'] + . $headers[$name]; + } + } + } + } + return $headers; + } + + protected function request_streams($url, $method='GET', $params=array(), $update_claimed_id) + { + if(!$this->hostExists($url)) { + throw new ErrorException("Could not connect to $url.", 404); + } + + if (empty($this->cnmatch)) { + $this->cnmatch = parse_url($url, PHP_URL_HOST); + } + + $params = http_build_query($params, '', '&'); + switch($method) { + case 'GET': + $opts = array( + 'http' => array( + 'method' => 'GET', + 'header' => 'Accept: application/xrds+xml, */*', + 'user_agent' => $this->user_agent, + 'ignore_errors' => true, + ), + 'ssl' => array( + 'CN_match' => $this->cnmatch + ) + ); + $url = $url . ($params ? '?' . $params : ''); + if (!empty($this->proxy)) { + $opts['http']['proxy'] = $this->proxy_url(); + } + break; + case 'POST': + $opts = array( + 'http' => array( + 'method' => 'POST', + 'header' => 'Content-type: application/x-www-form-urlencoded', + 'user_agent' => $this->user_agent, + 'content' => $params, + 'ignore_errors' => true, + ), + 'ssl' => array( + 'CN_match' => $this->cnmatch + ) + ); + if (!empty($this->proxy)) { + $opts['http']['proxy'] = $this->proxy_url(); + } + break; + case 'HEAD': + // We want to send a HEAD request, but since get_headers() doesn't + // accept $context parameter, we have to change the defaults. + $default = stream_context_get_options(stream_context_get_default()); + + // PHP does not reset all options. Instead, it just sets the options + // available in the passed array, therefore set the defaults manually. + $default += array( + 'http' => array(), + 'ssl' => array() + ); + $default['http'] += array( + 'method' => 'GET', + 'header' => '', + 'user_agent' => '', + 'ignore_errors' => false + ); + $default['ssl'] += array( + 'CN_match' => '' + ); + + $opts = array( + 'http' => array( + 'method' => 'HEAD', + 'header' => 'Accept: application/xrds+xml, */*', + 'user_agent' => $this->user_agent, + 'ignore_errors' => true, + ), + 'ssl' => array( + 'CN_match' => $this->cnmatch + ) + ); + + // Enable validation of the SSL certificates. + if ($this->verify_peer) { + $default['ssl'] += array( + 'verify_peer' => false, + 'capath' => '', + 'cafile' => '' + ); + $opts['ssl'] += array( + 'verify_peer' => true, + 'capath' => $this->capath, + 'cafile' => $this->cainfo + ); + } + + // Change the stream context options. + stream_context_get_default($opts); + + $headers = get_headers($url . ($params ? '?' . $params : '')); + + // Restore the stream context options. + stream_context_get_default($default); + + if (!empty($headers)) { + if (intval(substr($headers[0], strlen('HTTP/1.1 '))) == 405) { + // The server doesn't support HEAD - emulate it with a GET. + $args = func_get_args(); + $args[1] = 'GET'; + call_user_func_array(array($this, 'request_streams'), $args); + $headers = $this->headers; + } else { + $headers = $this->parse_header_array($headers, $update_claimed_id); + } + } else { + $headers = array(); + } + + return $headers; + } + + if ($this->verify_peer) { + $opts['ssl'] += array( + 'verify_peer' => true, + 'capath' => $this->capath, + 'cafile' => $this->cainfo + ); + } + + $context = stream_context_create ($opts); + $data = file_get_contents($url, false, $context); + # This is a hack for providers who don't support HEAD requests. + # It just creates the headers array for the last request in $this->headers. + if(isset($http_response_header)) { + $this->headers = $this->parse_header_array($http_response_header, $update_claimed_id); + } + + return $data; + } + + protected function request($url, $method='GET', $params=array(), $update_claimed_id=false) + { + $use_curl = false; + + if (function_exists('curl_init')) { + if (!$use_curl) { + # When allow_url_fopen is disabled, PHP streams will not work. + $use_curl = !ini_get('allow_url_fopen'); + } + + if (!$use_curl) { + # When there is no HTTPS wrapper, PHP streams cannott be used. + $use_curl = !in_array('https', stream_get_wrappers()); + } + + if (!$use_curl) { + # With open_basedir or safe_mode set, cURL can't follow redirects. + $use_curl = !(ini_get('safe_mode') || ini_get('open_basedir')); + } + } + + return + $use_curl + ? $this->request_curl($url, $method, $params, $update_claimed_id) + : $this->request_streams($url, $method, $params, $update_claimed_id); + } + + protected function proxy_url() + { + $result = ''; + + if (!empty($this->proxy)) { + $result = $this->proxy['host']; + + if (!empty($this->proxy['port'])) { + $result = $result . ':' . $this->proxy['port']; + } + + if (!empty($this->proxy['user'])) { + $result = $this->proxy['user'] . ':' . $this->proxy['pass'] . '@' . $result; + } + + $result = 'http://' . $result; + } + + return $result; + } + + protected function build_url($url, $parts) + { + if (isset($url['query'], $parts['query'])) { + $parts['query'] = $url['query'] . '&' . $parts['query']; + } + + $url = $parts + $url; + $url = $url['scheme'] . '://' + . (empty($url['username'])?'' + :(empty($url['password'])? "{$url['username']}@" + :"{$url['username']}:{$url['password']}@")) + . $url['host'] + . (empty($url['port'])?'':":{$url['port']}") + . (empty($url['path'])?'':$url['path']) + . (empty($url['query'])?'':"?{$url['query']}") + . (empty($url['fragment'])?'':"#{$url['fragment']}"); + return $url; + } + + /** + * Helper function used to scan for / tags and extract information + * from them + */ + protected function htmlTag($content, $tag, $attrName, $attrValue, $valueName) + { + preg_match_all("#<{$tag}[^>]*$attrName=['\"].*?$attrValue.*?['\"][^>]*$valueName=['\"](.+?)['\"][^>]*/?>#i", $content, $matches1); + preg_match_all("#<{$tag}[^>]*$valueName=['\"](.+?)['\"][^>]*$attrName=['\"].*?$attrValue.*?['\"][^>]*/?>#i", $content, $matches2); + + $result = array_merge($matches1[1], $matches2[1]); + return empty($result)?false:$result[0]; + } + + /** + * Performs Yadis and HTML discovery. Normally not used. + * @param $url Identity URL. + * @return String OP Endpoint (i.e. OpenID provider address). + * @throws ErrorException + */ + function discover($url) + { + if (!$url) throw new ErrorException('No identity supplied.'); + # Use xri.net proxy to resolve i-name identities + if (!preg_match('#^https?:#', $url)) { + $url = "https://xri.net/$url"; + } + + # We save the original url in case of Yadis discovery failure. + # It can happen when we'll be lead to an XRDS document + # which does not have any OpenID2 services. + $originalUrl = $url; + + # A flag to disable yadis discovery in case of failure in headers. + $yadis = true; + + # Allows optional regex replacement of the URL, e.g. to use Google Apps + # as an OpenID provider without setting up XRDS on the domain hosting. + if (!is_null($this->xrds_override_pattern) && !is_null($this->xrds_override_replacement)) { + $url = preg_replace($this->xrds_override_pattern, $this->xrds_override_replacement, $url); + } + + # We'll jump a maximum of 5 times, to avoid endless redirections. + for ($i = 0; $i < 5; $i ++) { + if ($yadis) { + $headers = $this->request($url, 'HEAD', array(), true); + + $next = false; + if (isset($headers['x-xrds-location'])) { + $url = $this->build_url(parse_url($url), parse_url(trim($headers['x-xrds-location']))); + $next = true; + } + + if (isset($headers['content-type']) && $this->is_allowed_type($headers['content-type'])) { + # Found an XRDS document, now let's find the server, and optionally delegate. + $content = $this->request($url, 'GET'); + + preg_match_all('#(.*?)#s', $content, $m); + foreach($m[1] as $content) { + $content = ' ' . $content; # The space is added, so that strpos doesn't return 0. + + # OpenID 2 + $ns = preg_quote('http://specs.openid.net/auth/2.0/', '#'); + if(preg_match('#\s*'.$ns.'(server|signon)\s*#s', $content, $type)) { + if ($type[1] == 'server') $this->identifier_select = true; + + preg_match('#(.*)#', $content, $server); + preg_match('#<(Local|Canonical)ID>(.*)#', $content, $delegate); + if (empty($server)) { + return false; + } + # Does the server advertise support for either AX or SREG? + $this->ax = (bool) strpos($content, 'http://openid.net/srv/ax/1.0'); + $this->sreg = strpos($content, 'http://openid.net/sreg/1.0') + || strpos($content, 'http://openid.net/extensions/sreg/1.1'); + + $server = $server[1]; + if (isset($delegate[2])) $this->identity = trim($delegate[2]); + $this->version = 2; + + $this->server = $server; + return $server; + } + + # OpenID 1.1 + $ns = preg_quote('http://openid.net/signon/1.1', '#'); + if (preg_match('#\s*'.$ns.'\s*#s', $content)) { + + preg_match('#(.*)#', $content, $server); + preg_match('#<.*?Delegate>(.*)#', $content, $delegate); + if (empty($server)) { + return false; + } + # AX can be used only with OpenID 2.0, so checking only SREG + $this->sreg = strpos($content, 'http://openid.net/sreg/1.0') + || strpos($content, 'http://openid.net/extensions/sreg/1.1'); + + $server = $server[1]; + if (isset($delegate[1])) $this->identity = $delegate[1]; + $this->version = 1; + + $this->server = $server; + return $server; + } + } + + $next = true; + $yadis = false; + $url = $originalUrl; + $content = null; + break; + } + if ($next) continue; + + # There are no relevant information in headers, so we search the body. + $content = $this->request($url, 'GET', array(), true); + + if (isset($this->headers['x-xrds-location'])) { + $url = $this->build_url(parse_url($url), parse_url(trim($this->headers['x-xrds-location']))); + continue; + } + + $location = $this->htmlTag($content, 'meta', 'http-equiv', 'X-XRDS-Location', 'content'); + if ($location) { + $url = $this->build_url(parse_url($url), parse_url($location)); + continue; + } + } + + if (!$content) $content = $this->request($url, 'GET'); + + # At this point, the YADIS Discovery has failed, so we'll switch + # to openid2 HTML discovery, then fallback to openid 1.1 discovery. + $server = $this->htmlTag($content, 'link', 'rel', 'openid2.provider', 'href'); + $delegate = $this->htmlTag($content, 'link', 'rel', 'openid2.local_id', 'href'); + $this->version = 2; + + if (!$server) { + # The same with openid 1.1 + $server = $this->htmlTag($content, 'link', 'rel', 'openid.server', 'href'); + $delegate = $this->htmlTag($content, 'link', 'rel', 'openid.delegate', 'href'); + $this->version = 1; + } + + if ($server) { + # We found an OpenID2 OP Endpoint + if ($delegate) { + # We have also found an OP-Local ID. + $this->identity = $delegate; + } + $this->server = $server; + return $server; + } + + throw new ErrorException("No OpenID Server found at $url", 404); + } + throw new ErrorException('Endless redirection!', 500); + } + + protected function is_allowed_type($content_type) { + # Apparently, some providers return XRDS documents as text/html. + # While it is against the spec, allowing this here shouldn't break + # compatibility with anything. + $allowed_types = array('application/xrds+xml', 'text/html', 'text/xml'); + + foreach ($allowed_types as $type) { + if (strpos($content_type, $type) !== false) { + return true; + } + } + + return false; + } + + protected function sregParams() + { + $params = array(); + # We always use SREG 1.1, even if the server is advertising only support for 1.0. + # That's because it's fully backwards compatibile with 1.0, and some providers + # advertise 1.0 even if they accept only 1.1. One such provider is myopenid.com + $params['openid.ns.sreg'] = 'http://openid.net/extensions/sreg/1.1'; + if ($this->required) { + $params['openid.sreg.required'] = array(); + foreach ($this->required as $required) { + if (!isset(self::$ax_to_sreg[$required])) continue; + $params['openid.sreg.required'][] = self::$ax_to_sreg[$required]; + } + $params['openid.sreg.required'] = implode(',', $params['openid.sreg.required']); + } + + if ($this->optional) { + $params['openid.sreg.optional'] = array(); + foreach ($this->optional as $optional) { + if (!isset(self::$ax_to_sreg[$optional])) continue; + $params['openid.sreg.optional'][] = self::$ax_to_sreg[$optional]; + } + $params['openid.sreg.optional'] = implode(',', $params['openid.sreg.optional']); + } + return $params; + } + + protected function axParams() + { + $params = array(); + if ($this->required || $this->optional) { + $params['openid.ns.ax'] = 'http://openid.net/srv/ax/1.0'; + $params['openid.ax.mode'] = 'fetch_request'; + $this->aliases = array(); + $counts = array(); + $required = array(); + $optional = array(); + foreach (array('required','optional') as $type) { + foreach ($this->$type as $alias => $field) { + if (is_int($alias)) $alias = strtr($field, '/', '_'); + $this->aliases[$alias] = 'http://axschema.org/' . $field; + if (empty($counts[$alias])) $counts[$alias] = 0; + $counts[$alias] += 1; + ${$type}[] = $alias; + } + } + foreach ($this->aliases as $alias => $ns) { + $params['openid.ax.type.' . $alias] = $ns; + } + foreach ($counts as $alias => $count) { + if ($count == 1) continue; + $params['openid.ax.count.' . $alias] = $count; + } + + # Don't send empty ax.requied and ax.if_available. + # Google and possibly other providers refuse to support ax when one of these is empty. + if($required) { + $params['openid.ax.required'] = implode(',', $required); + } + if($optional) { + $params['openid.ax.if_available'] = implode(',', $optional); + } + } + return $params; + } + + protected function authUrl_v1($immediate) + { + $returnUrl = $this->returnUrl; + # If we have an openid.delegate that is different from our claimed id, + # we need to somehow preserve the claimed id between requests. + # The simplest way is to just send it along with the return_to url. + if($this->identity != $this->claimed_id) { + $returnUrl .= (strpos($returnUrl, '?') ? '&' : '?') . 'openid.claimed_id=' . $this->claimed_id; + } + + $params = array( + 'openid.return_to' => $returnUrl, + 'openid.mode' => $immediate ? 'checkid_immediate' : 'checkid_setup', + 'openid.identity' => $this->identity, + 'openid.trust_root' => $this->trustRoot, + ) + $this->sregParams(); + + return $this->build_url(parse_url($this->server) + , array('query' => http_build_query($params, '', '&'))); + } + + protected function authUrl_v2($immediate) + { + $params = array( + 'openid.ns' => 'http://specs.openid.net/auth/2.0', + 'openid.mode' => $immediate ? 'checkid_immediate' : 'checkid_setup', + 'openid.return_to' => $this->returnUrl, + 'openid.realm' => $this->trustRoot, + ); + + if ($this->ax) { + $params += $this->axParams(); + } + + if ($this->sreg) { + $params += $this->sregParams(); + } + + if (!$this->ax && !$this->sreg) { + # If OP doesn't advertise either SREG, nor AX, let's send them both + # in worst case we don't get anything in return. + $params += $this->axParams() + $this->sregParams(); + } + + if (!empty($this->oauth) && is_array($this->oauth)) { + $params['openid.ns.oauth'] = 'http://specs.openid.net/extensions/oauth/1.0'; + $params['openid.oauth.consumer'] = str_replace(array('http://', 'https://'), '', $this->trustRoot); + $params['openid.oauth.scope'] = implode(' ', $this->oauth); + } + + if ($this->identifier_select) { + $params['openid.identity'] = $params['openid.claimed_id'] + = 'http://specs.openid.net/auth/2.0/identifier_select'; + } else { + $params['openid.identity'] = $this->identity; + $params['openid.claimed_id'] = $this->claimed_id; + } + + return $this->build_url(parse_url($this->server) + , array('query' => http_build_query($params, '', '&'))); + } + + /** + * Returns authentication url. Usually, you want to redirect your user to it. + * @return String The authentication url. + * @param String $select_identifier Whether to request OP to select identity for an user in OpenID 2. Does not affect OpenID 1. + * @throws ErrorException + */ + function authUrl($immediate = false) + { + if ($this->setup_url && !$immediate) return $this->setup_url; + if (!$this->server) $this->discover($this->identity); + + if ($this->version == 2) { + return $this->authUrl_v2($immediate); + } + return $this->authUrl_v1($immediate); + } + + /** + * Performs OpenID verification with the OP. + * @return Bool Whether the verification was successful. + * @throws ErrorException + */ + function validate() + { + # If the request was using immediate mode, a failure may be reported + # by presenting user_setup_url (for 1.1) or reporting + # mode 'setup_needed' (for 2.0). Also catching all modes other than + # id_res, in order to avoid throwing errors. + if(isset($this->data['openid_user_setup_url'])) { + $this->setup_url = $this->data['openid_user_setup_url']; + return false; + } + if($this->mode != 'id_res') { + return false; + } + + $this->claimed_id = isset($this->data['openid_claimed_id'])?$this->data['openid_claimed_id']:$this->data['openid_identity']; + $params = array( + 'openid.assoc_handle' => $this->data['openid_assoc_handle'], + 'openid.signed' => $this->data['openid_signed'], + 'openid.sig' => $this->data['openid_sig'], + ); + + if (isset($this->data['openid_ns'])) { + # We're dealing with an OpenID 2.0 server, so let's set an ns + # Even though we should know location of the endpoint, + # we still need to verify it by discovery, so $server is not set here + $params['openid.ns'] = 'http://specs.openid.net/auth/2.0'; + } elseif (isset($this->data['openid_claimed_id']) + && $this->data['openid_claimed_id'] != $this->data['openid_identity'] + ) { + # If it's an OpenID 1 provider, and we've got claimed_id, + # we have to append it to the returnUrl, like authUrl_v1 does. + $this->returnUrl .= (strpos($this->returnUrl, '?') ? '&' : '?') + . 'openid.claimed_id=' . $this->claimed_id; + } + + if ($this->data['openid_return_to'] != $this->returnUrl) { + # The return_to url must match the url of current request. + # I'm assuing that noone will set the returnUrl to something that doesn't make sense. + return false; + } + + $server = $this->discover($this->claimed_id); + + foreach (explode(',', $this->data['openid_signed']) as $item) { + # Checking whether magic_quotes_gpc is turned on, because + # the function may fail if it is. For example, when fetching + # AX namePerson, it might containg an apostrophe, which will be escaped. + # In such case, validation would fail, since we'd send different data than OP + # wants to verify. stripslashes() should solve that problem, but we can't + # use it when magic_quotes is off. + $value = $this->data['openid_' . str_replace('.','_',$item)]; + $params['openid.' . $item] = function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc() ? stripslashes($value) : $value; + + } + + $params['openid.mode'] = 'check_authentication'; + + $response = $this->request($server, 'POST', $params); + + return preg_match('/is_valid\s*:\s*true/i', $response); + } + + protected function getAxAttributes() + { + $result = array(); + + if ($alias = $this->getNamespaceAlias('http://openid.net/srv/ax/1.0', 'ax')) { + $prefix = 'openid_' . $alias; + $length = strlen('http://axschema.org/'); + + foreach (explode(',', $this->data['openid_signed']) as $key) { + $keyMatch = $alias . '.type.'; + + if (strncmp($key, $keyMatch, strlen($keyMatch)) !== 0) { + continue; + } + + $key = substr($key, strlen($keyMatch)); + $idv = $prefix . '_value_' . $key; + $idc = $prefix . '_count_' . $key; + $key = substr($this->getItem($prefix . '_type_' . $key), $length); + + if (!empty($key)) { + if (($count = intval($this->getItem($idc))) > 0) { + $value = array(); + + for ($i = 1; $i <= $count; $i++) { + $value[] = $this->getItem($idv . '_' . $i); + } + + $value = ($count == 1) ? reset($value) : $value; + } else { + $value = $this->getItem($idv); + } + + if (!is_null($value)) { + $result[$key] = $value; + } + } + } + } else { + // No alias for the AX schema has been found, + // so there is no AX data in the OP's response. + } + + return $result; + } + + protected function getSregAttributes() + { + $attributes = array(); + $sreg_to_ax = array_flip(self::$ax_to_sreg); + foreach (explode(',', $this->data['openid_signed']) as $key) { + $keyMatch = 'sreg.'; + if (strncmp($key, $keyMatch, strlen($keyMatch)) !== 0) { + continue; + } + $key = substr($key, strlen($keyMatch)); + if (!isset($sreg_to_ax[$key])) { + # The field name isn't part of the SREG spec, so we ignore it. + continue; + } + $attributes[$sreg_to_ax[$key]] = $this->data['openid_sreg_' . $key]; + } + return $attributes; + } + + /** + * Gets AX/SREG attributes provided by OP. should be used only after successful validaton. + * Note that it does not guarantee that any of the required/optional parameters will be present, + * or that there will be no other attributes besides those specified. + * In other words. OP may provide whatever information it wants to. + * * SREG names will be mapped to AX names. + * * @return Array Array of attributes with keys being the AX schema names, e.g. 'contact/email' + * @see http://www.axschema.org/types/ + */ + function getAttributes() + { + if (isset($this->data['openid_ns']) + && $this->data['openid_ns'] == 'http://specs.openid.net/auth/2.0' + ) { # OpenID 2.0 + # We search for both AX and SREG attributes, with AX taking precedence. + return $this->getAxAttributes() + $this->getSregAttributes(); + } + return $this->getSregAttributes(); + } + + /** + * Gets an OAuth request token if the OpenID+OAuth hybrid protocol has been used. + * + * In order to use the OpenID+OAuth hybrid protocol, you need to add at least one + * scope to the $openid->oauth array before you get the call to getAuthUrl(), e.g.: + * $openid->oauth[] = 'https://www.googleapis.com/auth/plus.me'; + * + * Furthermore the registered consumer name must fit the OpenID realm. + * To register an OpenID consumer at Google use: https://www.google.com/accounts/ManageDomains + * + * @return string|bool OAuth request token on success, FALSE if no token was provided. + */ + function getOAuthRequestToken() + { + $alias = $this->getNamespaceAlias('http://specs.openid.net/extensions/oauth/1.0'); + + return !empty($alias) ? $this->data['openid_' . $alias . '_request_token'] : false; + } + + /** + * Gets the alias for the specified namespace, if it's present. + * + * @param string $namespace The namespace for which an alias is needed. + * @param string $hint Common alias of this namespace, used for optimization. + * @return string|null The namespace alias if found, otherwise - NULL. + */ + private function getNamespaceAlias($namespace, $hint = null) + { + $result = null; + + if (empty($hint) || $this->getItem('openid_ns_' . $hint) != $namespace) { + // The common alias is either undefined or points to + // some other extension - search for another alias.. + $prefix = 'openid_ns_'; + $length = strlen($prefix); + + foreach ($this->data as $key => $val) { + if (strncmp($key, $prefix, $length) === 0 && $val === $namespace) { + $result = trim(substr($key, $length)); + break; + } + } + } else { + $result = $hint; + } + + return $result; + } + + /** + * Gets an item from the $data array by the specified id. + * + * @param string $id The id of the desired item. + * @return string|null The item if found, otherwise - NULL. + */ + private function getItem($id) + { + return isset($this->data[$id]) ? $this->data[$id] : null; + } +} diff --git a/hauth/Hybrid/thirdparty/Paypal/PaypalOAuth2Client.php b/hauth/Hybrid/thirdparty/Paypal/PaypalOAuth2Client.php new file mode 100644 index 0000000..150c735 --- /dev/null +++ b/hauth/Hybrid/thirdparty/Paypal/PaypalOAuth2Client.php @@ -0,0 +1,142 @@ + "authorization_code", + "code" => $code, + "redirect_uri" => $this->redirect_uri, + ); + + $response = $this->request( $this->token_url, $params, $this->curl_authenticate_method ); + + $response = $this->parseRequestResult( $response ); + + if( ! $response || ! isset( $response->access_token ) ){ + throw new Exception( "The Authorization Service has return: " . $response->message ); + } + + 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; + } + + + // -- tokens + + public function tokenInfo($accesstoken) + { + $params['access_token'] = $this->access_token; + $response = $this->request( $this->token_info_url, $params, "POST" ); + return $this->parseRequestResult( $response ); + } + + public function refreshToken( $params = array() ) + { + $params = array( + "grant_type" => "refresh_token", + "refresh_token" => $this->refresh_token, + ); + $response = $this->request( $this->token_url, $params, "POST" ); + return $this->parseRequestResult( $response ); + } + + // -- utilities + + private function request( $url, $params=false, $type="GET" ) + { + $params = http_build_query($params, '', '&'); + Hybrid_Logger::info( "Enter OAuth2Client::request( $url )" ); + Hybrid_Logger::debug( "OAuth2Client::request(). dump request params: ", $params ); + + if( $type == "GET" ){ + $url = $url . ( strpos( $url, '?' ) ? '&' : '?' ) . $params; + } + + $this->http_info = array(); + $ch = curl_init(); + + $headers = $this->curl_header; + if($type == "POST" ){ + //$headers[] = 'Content-Type: application/x-www-form-urlencoded'; + } + + 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 , $headers ); + curl_setopt($ch, CURLOPT_USERPWD , $this->client_id.':'.$this->client_secret ); + // logging + if ($this->curl_log !== null) { + $fp = fopen($this->curl_log, 'a'); + curl_setopt($ch, CURLOPT_STDERR , $fp ); + curl_setopt($ch, CURLOPT_VERBOSE , 1 ); + } + + if($this->curl_proxy){ + curl_setopt( $ch, CURLOPT_PROXY , $this->curl_proxy); + } + + if( $type == "POST" ){ + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $params ); + } + + $response = curl_exec($ch); + if ($this->curl_log !== null) + fclose($fp); + 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/hauth/Hybrid/thirdparty/index.html b/hauth/Hybrid/thirdparty/index.html new file mode 100644 index 0000000..065d2da --- /dev/null +++ b/hauth/Hybrid/thirdparty/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + \ No newline at end of file diff --git a/hauth/connect.php b/hauth/connect.php new file mode 100644 index 0000000..e85c419 --- /dev/null +++ b/hauth/connect.php @@ -0,0 +1,72 @@ +authenticate( $_REQUEST['provider'], $gBitUser ); + + } catch( Exception $e ) { + vd( $e ); + // Display the recived error, + // to know more please refer to Exceptions handling section on the userguide + switch( $e->getCode() ){ + case 0 : echo "Unspecified error."; break; + case 1 : echo "Hybriauth configuration error."; break; + case 2 : echo "Provider not properly configured."; break; + case 3 : echo "Unknown or disabled provider."; break; + case 4 : echo "Missing provider application credentials."; break; + case 5 : echo "Authentification failed. The user has canceled the authentication or the provider refused the connection."; + break; + case 6 : echo "User profile request failed. Most likely the user is not connected to the provider and he should authenticate again."; + $authProfile->logout(); + break; + case 7 : echo "User not connected to the provider."; + $authProfile->logout(); + break; + case 8 : echo "Provider does not support this feature."; break; + } + + // well, basically your should not display this to the end user, just give him a hint and move on.. + echo "

Original error message: " . $e->getMessage(); + } + +} + +// but if we came from a login page, let's go home (except if we got an error when login in) +if( empty( $url ) ) { + $url = BitBase::getParameter( $_SERVER, 'HTTP_REFERER', $gBitSystem->getDefaultPage() ); +} + +bit_redirect( $url ); diff --git a/hauth/disconnect.php b/hauth/disconnect.php new file mode 100644 index 0000000..c64f565 --- /dev/null +++ b/hauth/disconnect.php @@ -0,0 +1,23 @@ +