diff --git a/main/auth/external_login/facebook-php-sdk/autoload.php b/main/auth/external_login/facebook-php-sdk/autoload.php new file mode 100644 index 0000000000..e96ec65043 --- /dev/null +++ b/main/auth/external_login/facebook-php-sdk/autoload.php @@ -0,0 +1,70 @@ +accessToken = $accessToken; + if ($expiresAt) { + $this->setExpiresAtFromTimeStamp($expiresAt); + } + $this->machineId = $machineId; + } + + /** + * Setter for expires_at. + * + * @param int $timeStamp + */ + protected function setExpiresAtFromTimeStamp($timeStamp) + { + $dt = new \DateTime(); + $dt->setTimestamp($timeStamp); + $this->expiresAt = $dt; + } + + /** + * Getter for expiresAt. + * + * @return \DateTime|null + */ + public function getExpiresAt() + { + return $this->expiresAt; + } + + /** + * Getter for machineId. + * + * @return string|null + */ + public function getMachineId() + { + return $this->machineId; + } + + /** + * Determines whether or not this is a long-lived token. + * + * @return bool + */ + public function isLongLived() + { + if ($this->expiresAt) { + return $this->expiresAt->getTimestamp() > time() + (60 * 60 * 2); + } + return false; + } + + /** + * Checks the validity of the access token. + * + * @param string|null $appId Application ID to use + * @param string|null $appSecret App secret value to use + * @param string|null $machineId + * + * @return boolean + */ + public function isValid($appId = null, $appSecret = null, $machineId = null) + { + $accessTokenInfo = $this->getInfo($appId, $appSecret); + $machineId = $machineId ?: $this->machineId; + return static::validateAccessToken($accessTokenInfo, $appId, $machineId); + } + + /** + * Ensures the provided GraphSessionInfo object is valid, + * throwing an exception if not. Ensures the appId matches, + * that the machineId matches if it's being used, + * that the token is valid and has not expired. + * + * @param GraphSessionInfo $tokenInfo + * @param string|null $appId Application ID to use + * @param string|null $machineId + * + * @return boolean + */ + public static function validateAccessToken(GraphSessionInfo $tokenInfo, + $appId = null, $machineId = null) + { + $targetAppId = FacebookSession::_getTargetAppId($appId); + + $appIdIsValid = $tokenInfo->getAppId() == $targetAppId; + $machineIdIsValid = $tokenInfo->getProperty('machine_id') == $machineId; + $accessTokenIsValid = $tokenInfo->isValid(); + + // Not all access tokens return an expiration. E.g. an app access token. + if ($tokenInfo->getExpiresAt() instanceof \DateTime) { + $accessTokenIsStillAlive = $tokenInfo->getExpiresAt()->getTimestamp() >= time(); + } else { + $accessTokenIsStillAlive = true; + } + + return $appIdIsValid && $machineIdIsValid && $accessTokenIsValid && $accessTokenIsStillAlive; + } + + /** + * Get a valid access token from a code. + * + * @param string $code + * @param string|null $appId + * @param string|null $appSecret + * @param string|null $machineId + * + * @return AccessToken + */ + public static function getAccessTokenFromCode($code, $appId = null, $appSecret = null, $machineId = null) + { + $params = array( + 'code' => $code, + 'redirect_uri' => '', + ); + + if ($machineId) { + $params['machine_id'] = $machineId; + } + + return static::requestAccessToken($params, $appId, $appSecret); + } + + /** + * Get a valid code from an access token. + * + * @param AccessToken|string $accessToken + * @param string|null $appId + * @param string|null $appSecret + * + * @return AccessToken + */ + public static function getCodeFromAccessToken($accessToken, $appId = null, $appSecret = null) + { + $accessToken = (string) $accessToken; + + $params = array( + 'access_token' => $accessToken, + 'redirect_uri' => '', + ); + + return static::requestCode($params, $appId, $appSecret); + } + + /** + * Exchanges a short lived access token with a long lived access token. + * + * @param string|null $appId + * @param string|null $appSecret + * + * @return AccessToken + */ + public function extend($appId = null, $appSecret = null) + { + $params = array( + 'grant_type' => 'fb_exchange_token', + 'fb_exchange_token' => $this->accessToken, + ); + + return static::requestAccessToken($params, $appId, $appSecret); + } + + /** + * Request an access token based on a set of params. + * + * @param array $params + * @param string|null $appId + * @param string|null $appSecret + * + * @return AccessToken + * + * @throws FacebookRequestException + */ + public static function requestAccessToken(array $params, $appId = null, $appSecret = null) + { + $response = static::request('/oauth/access_token', $params, $appId, $appSecret); + $data = $response->getResponse(); + + /** + * @TODO fix this malarkey - getResponse() should always return an object + * @see https://github.com/facebook/facebook-php-sdk-v4/issues/36 + */ + if (is_array($data)) { + if (isset($data['access_token'])) { + $expiresAt = isset($data['expires']) ? time() + $data['expires'] : 0; + return new static($data['access_token'], $expiresAt); + } + } elseif($data instanceof \stdClass) { + if (isset($data->access_token)) { + $expiresAt = isset($data->expires_in) ? time() + $data->expires_in : 0; + $machineId = isset($data->machine_id) ? (string) $data->machine_id : null; + return new static((string) $data->access_token, $expiresAt, $machineId); + } + } + + throw FacebookRequestException::create( + $response->getRawResponse(), + $data, + 401 + ); + } + + /** + * Request a code from a long lived access token. + * + * @param array $params + * @param string|null $appId + * @param string|null $appSecret + * + * @return string + * + * @throws FacebookRequestException + */ + public static function requestCode(array $params, $appId = null, $appSecret = null) + { + $response = static::request('/oauth/client_code', $params, $appId, $appSecret); + $data = $response->getResponse(); + + if (isset($data->code)) { + return (string) $data->code; + } + + throw FacebookRequestException::create( + $response->getRawResponse(), + $data, + 401 + ); + } + + /** + * Send a request to Graph with an app access token. + * + * @param string $endpoint + * @param array $params + * @param string|null $appId + * @param string|null $appSecret + * + * @return \Facebook\FacebookResponse + * + * @throws FacebookRequestException + */ + protected static function request($endpoint, array $params, $appId = null, $appSecret = null) + { + $targetAppId = FacebookSession::_getTargetAppId($appId); + $targetAppSecret = FacebookSession::_getTargetAppSecret($appSecret); + + if (!isset($params['client_id'])) { + $params['client_id'] = $targetAppId; + } + if (!isset($params['client_secret'])) { + $params['client_secret'] = $targetAppSecret; + } + + // The response for this endpoint is not JSON, so it must be handled + // differently, not as a GraphObject. + $request = new FacebookRequest( + FacebookSession::newAppSession($targetAppId, $targetAppSecret), + 'GET', + $endpoint, + $params + ); + return $request->execute(); + } + + /** + * Get more info about an access token. + * + * @param string|null $appId + * @param string|null $appSecret + * + * @return GraphSessionInfo + */ + public function getInfo($appId = null, $appSecret = null) + { + $params = array('input_token' => $this->accessToken); + + $request = new FacebookRequest( + FacebookSession::newAppSession($appId, $appSecret), + 'GET', + '/debug_token', + $params + ); + $response = $request->execute()->getGraphObject(GraphSessionInfo::className()); + + // Update the data on this token + if ($response->getExpiresAt()) { + $this->expiresAt = $response->getExpiresAt(); + } + + return $response; + } + + /** + * Returns the access token as a string. + * + * @return string + */ + public function __toString() + { + return $this->accessToken; + } + + /** + * Returns true if the access token is an app session token. + * + * @return bool + */ + public function isAppSession() + { + return strpos($this->accessToken, "|") !== false; + } + +} diff --git a/main/auth/external_login/facebook-php-sdk/src/Facebook/Entities/SignedRequest.php b/main/auth/external_login/facebook-php-sdk/src/Facebook/Entities/SignedRequest.php new file mode 100644 index 0000000000..09134c5bf2 --- /dev/null +++ b/main/auth/external_login/facebook-php-sdk/src/Facebook/Entities/SignedRequest.php @@ -0,0 +1,386 @@ +rawSignedRequest = $rawSignedRequest; + $this->payload = static::parse($rawSignedRequest, $state, $appSecret); + } + + /** + * Returns the raw signed request data. + * + * @return string|null + */ + public function getRawSignedRequest() + { + return $this->rawSignedRequest; + } + + /** + * Returns the parsed signed request data. + * + * @return array|null + */ + public function getPayload() + { + return $this->payload; + } + + /** + * Returns a property from the signed request data if available. + * + * @param string $key + * @param mixed|null $default + * + * @return mixed|null + */ + public function get($key, $default = null) + { + if (isset($this->payload[$key])) { + return $this->payload[$key]; + } + return $default; + } + + /** + * Returns user_id from signed request data if available. + * + * @return string|null + */ + public function getUserId() + { + return $this->get('user_id'); + } + + /** + * Checks for OAuth data in the payload. + * + * @return boolean + */ + public function hasOAuthData() + { + return isset($this->payload['oauth_token']) || isset($this->payload['code']); + } + + /** + * Creates a signed request from an array of data. + * + * @param array $payload + * @param string|null $appSecret + * + * @return string + */ + public static function make(array $payload, $appSecret = null) + { + $payload['algorithm'] = 'HMAC-SHA256'; + $payload['issued_at'] = time(); + $encodedPayload = static::base64UrlEncode(json_encode($payload)); + + $hashedSig = static::hashSignature($encodedPayload, $appSecret); + $encodedSig = static::base64UrlEncode($hashedSig); + + return $encodedSig.'.'.$encodedPayload; + } + + /** + * Validates and decodes a signed request and returns + * the payload as an array. + * + * @param string $signedRequest + * @param string|null $state + * @param string|null $appSecret + * + * @return array + */ + public static function parse($signedRequest, $state = null, $appSecret = null) + { + list($encodedSig, $encodedPayload) = static::split($signedRequest); + + // Signature validation + $sig = static::decodeSignature($encodedSig); + $hashedSig = static::hashSignature($encodedPayload, $appSecret); + static::validateSignature($hashedSig, $sig); + + // Payload validation + $data = static::decodePayload($encodedPayload); + static::validateAlgorithm($data); + if ($state) { + static::validateCsrf($data, $state); + } + + return $data; + } + + /** + * Validates the format of a signed request. + * + * @param string $signedRequest + * + * @throws FacebookSDKException + */ + public static function validateFormat($signedRequest) + { + if (strpos($signedRequest, '.') !== false) { + return; + } + + throw new FacebookSDKException( + 'Malformed signed request.', 606 + ); + } + + /** + * Decodes a raw valid signed request. + * + * @param string $signedRequest + * + * @returns array + */ + public static function split($signedRequest) + { + static::validateFormat($signedRequest); + + return explode('.', $signedRequest, 2); + } + + /** + * Decodes the raw signature from a signed request. + * + * @param string $encodedSig + * + * @returns string + * + * @throws FacebookSDKException + */ + public static function decodeSignature($encodedSig) + { + $sig = static::base64UrlDecode($encodedSig); + + if ($sig) { + return $sig; + } + + throw new FacebookSDKException( + 'Signed request has malformed encoded signature data.', 607 + ); + } + + /** + * Decodes the raw payload from a signed request. + * + * @param string $encodedPayload + * + * @returns array + * + * @throws FacebookSDKException + */ + public static function decodePayload($encodedPayload) + { + $payload = static::base64UrlDecode($encodedPayload); + + if ($payload) { + $payload = json_decode($payload, true); + } + + if (is_array($payload)) { + return $payload; + } + + throw new FacebookSDKException( + 'Signed request has malformed encoded payload data.', 607 + ); + } + + /** + * Validates the algorithm used in a signed request. + * + * @param array $data + * + * @throws FacebookSDKException + */ + public static function validateAlgorithm(array $data) + { + if (isset($data['algorithm']) && $data['algorithm'] === 'HMAC-SHA256') { + return; + } + + throw new FacebookSDKException( + 'Signed request is using the wrong algorithm.', 605 + ); + } + + /** + * Hashes the signature used in a signed request. + * + * @param string $encodedData + * @param string|null $appSecret + * + * @return string + * + * @throws FacebookSDKException + */ + public static function hashSignature($encodedData, $appSecret = null) + { + $hashedSig = hash_hmac( + 'sha256', $encodedData, FacebookSession::_getTargetAppSecret($appSecret), $raw_output = true + ); + + if ($hashedSig) { + return $hashedSig; + } + + throw new FacebookSDKException( + 'Unable to hash signature from encoded payload data.', 602 + ); + } + + /** + * Validates the signature used in a signed request. + * + * @param string $hashedSig + * @param string $sig + * + * @throws FacebookSDKException + */ + public static function validateSignature($hashedSig, $sig) + { + if (mb_strlen($hashedSig) === mb_strlen($sig)) { + $validate = 0; + for ($i = 0; $i < mb_strlen($sig); $i++) { + $validate |= ord($hashedSig[$i]) ^ ord($sig[$i]); + } + if ($validate === 0) { + return; + } + } + + throw new FacebookSDKException( + 'Signed request has an invalid signature.', 602 + ); + } + + /** + * Validates a signed request against CSRF. + * + * @param array $data + * @param string $state + * + * @throws FacebookSDKException + */ + public static function validateCsrf(array $data, $state) + { + if (isset($data['state']) && $data['state'] === $state) { + return; + } + + throw new FacebookSDKException( + 'Signed request did not pass CSRF validation.', 604 + ); + } + + /** + * Base64 decoding which replaces characters: + * + instead of - + * / instead of _ + * @link http://en.wikipedia.org/wiki/Base64#URL_applications + * + * @param string $input base64 url encoded input + * + * @return string decoded string + */ + public static function base64UrlDecode($input) + { + $urlDecodedBase64 = strtr($input, '-_', '+/'); + static::validateBase64($urlDecodedBase64); + return base64_decode($urlDecodedBase64); + } + + /** + * Base64 encoding which replaces characters: + * + instead of - + * / instead of _ + * @link http://en.wikipedia.org/wiki/Base64#URL_applications + * + * @param string $input string to encode + * + * @return string base64 url encoded input + */ + public static function base64UrlEncode($input) + { + return strtr(base64_encode($input), '+/', '-_'); + } + + /** + * Validates a base64 string. + * + * @param string $input base64 value to validate + * + * @throws FacebookSDKException + */ + public static function validateBase64($input) + { + $pattern = '/^[a-zA-Z0-9\/\r\n+]*={0,2}$/'; + if (preg_match($pattern, $input)) { + return; + } + + throw new FacebookSDKException( + 'Signed request contains malformed base64 encoding.', 608 + ); + } + +} diff --git a/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookAuthorizationException.php b/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookAuthorizationException.php new file mode 100644 index 0000000000..8a3a15c209 --- /dev/null +++ b/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookAuthorizationException.php @@ -0,0 +1,33 @@ + + * @author David Poll + */ +class FacebookCanvasLoginHelper extends FacebookSignedRequestFromInputHelper +{ + + /** + * Returns the app data value. + * + * @return mixed|null + */ + public function getAppData() + { + return $this->signedRequest ? $this->signedRequest->get('app_data') : null; + } + + /** + * Get raw signed request from POST. + * + * @return string|null + */ + public function getRawSignedRequest() + { + $rawSignedRequest = $this->getRawSignedRequestFromPost(); + if ($rawSignedRequest) { + return $rawSignedRequest; + } + + return null; + } + +} diff --git a/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookClientException.php b/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookClientException.php new file mode 100644 index 0000000000..0eaadae885 --- /dev/null +++ b/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookClientException.php @@ -0,0 +1,33 @@ + + * @author David Poll + */ +class FacebookJavaScriptLoginHelper extends FacebookSignedRequestFromInputHelper +{ + + /** + * Get raw signed request from the cookie. + * + * @return string|null + */ + public function getRawSignedRequest() + { + return $this->getRawSignedRequestFromCookie(); + } + +} diff --git a/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookOtherException.php b/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookOtherException.php new file mode 100644 index 0000000000..7d6fdf4d55 --- /dev/null +++ b/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookOtherException.php @@ -0,0 +1,33 @@ + + */ +class FacebookPageTabHelper extends FacebookCanvasLoginHelper +{ + + /** + * @var array|null + */ + protected $pageData; + + /** + * Initialize the helper and process available signed request data. + * + * @param string|null $appId + * @param string|null $appSecret + */ + public function __construct($appId = null, $appSecret = null) + { + parent::__construct($appId, $appSecret); + + if (!$this->signedRequest) { + return; + } + + $this->pageData = $this->signedRequest->get('page'); + } + + /** + * Returns a value from the page data. + * + * @param string $key + * @param mixed|null $default + * + * @return mixed|null + */ + public function getPageData($key, $default = null) + { + if (isset($this->pageData[$key])) { + return $this->pageData[$key]; + } + return $default; + } + + /** + * Returns true if the page is liked by the user. + * + * @return boolean + */ + public function isLiked() + { + return $this->getPageData('liked') === true; + } + + /** + * Returns true if the user is an admin. + * + * @return boolean + */ + public function isAdmin() + { + return $this->getPageData('admin') === true; + } + + /** + * Returns the page id if available. + * + * @return string|null + */ + public function getPageId() + { + return $this->getPageData('id'); + } + +} diff --git a/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookPermissionException.php b/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookPermissionException.php new file mode 100644 index 0000000000..7fe970c033 --- /dev/null +++ b/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookPermissionException.php @@ -0,0 +1,33 @@ + + * @author David Poll + */ +class FacebookRedirectLoginHelper +{ + + /** + * @var string The application id + */ + private $appId; + + /** + * @var string The application secret + */ + private $appSecret; + + /** + * @var string The redirect URL for the application + */ + private $redirectUrl; + + /** + * @var string Prefix to use for session variables + */ + private $sessionPrefix = 'FBRLH_'; + + /** + * @var string State token for CSRF validation + */ + protected $state; + + /** + * @var boolean Toggle for PHP session status check + */ + protected $checkForSessionStatus = true; + + /** + * Constructs a RedirectLoginHelper for a given appId and redirectUrl. + * + * @param string $redirectUrl The URL Facebook should redirect users to + * after login + * @param string $appId The application id + * @param string $appSecret The application secret + */ + public function __construct($redirectUrl, $appId = null, $appSecret = null) + { + $this->appId = FacebookSession::_getTargetAppId($appId); + $this->appSecret = FacebookSession::_getTargetAppSecret($appSecret); + $this->redirectUrl = $redirectUrl; + } + + /** + * Stores CSRF state and returns a URL to which the user should be sent to + * in order to continue the login process with Facebook. The + * provided redirectUrl should invoke the handleRedirect method. + * + * @param array $scope List of permissions to request during login + * @param string $version Optional Graph API version if not default (v2.0) + * @param boolean $displayAsPopup Indicate if the page will be displayed as a popup + * + * @return string + */ + public function getLoginUrl($scope = array(), $version = null, $displayAsPopup = false) + { + + $version = ($version ?: FacebookRequest::GRAPH_API_VERSION); + $this->state = $this->random(16); + $this->storeState($this->state); + + $params = array( + 'client_id' => $this->appId, + 'redirect_uri' => $this->redirectUrl, + 'state' => $this->state, + 'sdk' => 'php-sdk-' . FacebookRequest::VERSION, + 'scope' => implode(',', $scope) + ); + + if ($displayAsPopup) + { + $params['display'] = 'popup'; + } + + return 'https://www.facebook.com/' . $version . '/dialog/oauth?' . + http_build_query($params, null, '&'); + } + + /** + * Returns a URL to which the user should be sent to re-request permissions. + * + * @param array $scope List of permissions to re-request + * @param string $version Optional Graph API version if not default (v2.0) + * + * @return string + */ + public function getReRequestUrl($scope = array(), $version = null) + { + $version = ($version ?: FacebookRequest::GRAPH_API_VERSION); + $this->state = $this->random(16); + $this->storeState($this->state); + $params = array( + 'client_id' => $this->appId, + 'redirect_uri' => $this->redirectUrl, + 'state' => $this->state, + 'sdk' => 'php-sdk-' . FacebookRequest::VERSION, + 'auth_type' => 'rerequest', + 'scope' => implode(',', $scope) + ); + return 'https://www.facebook.com/' . $version . '/dialog/oauth?' . + http_build_query($params, null, '&'); + } + + /** + * Returns the URL to send the user in order to log out of Facebook. + * + * @param FacebookSession $session The session that will be logged out + * @param string $next The url Facebook should redirect the user to after + * a successful logout + * + * @return string + * + * @throws FacebookSDKException + */ + public function getLogoutUrl(FacebookSession $session, $next) + { + if ($session->getAccessToken()->isAppSession()) { + throw new FacebookSDKException( + 'Cannot generate a Logout URL with an App Session.', 722 + ); + } + $params = array( + 'next' => $next, + 'access_token' => $session->getToken() + ); + return 'https://www.facebook.com/logout.php?' . http_build_query($params, null, '&'); + } + + /** + * Handles a response from Facebook, including a CSRF check, and returns a + * FacebookSession. + * + * @return FacebookSession|null + */ + public function getSessionFromRedirect() + { + $this->loadState(); + if ($this->isValidRedirect()) { + + $params = array( + 'client_id' => FacebookSession::_getTargetAppId($this->appId), + 'redirect_uri' => $this->redirectUrl, + 'client_secret' => + FacebookSession::_getTargetAppSecret($this->appSecret), + 'code' => $this->getCode() + ); + + $response = (new FacebookRequest( + FacebookSession::newAppSession($this->appId, $this->appSecret), + 'GET', + '/oauth/access_token', + $params + ))->execute()->getResponse(); + + if (isset($response['access_token'])) { + return new FacebookSession($response['access_token']); + } + } + return null; + } + + /** + * Check if a redirect has a valid state. + * + * @return bool + */ + protected function isValidRedirect() + { + return $this->getCode() && isset($_GET['state']) + && $_GET['state'] == $this->state; + } + + /** + * Return the code. + * + * @return string|null + */ + protected function getCode() + { + + return isset($_GET['code']) ? $_GET['code'] : null; + } + + /** + * Stores a state string in session storage for CSRF protection. + * Developers should subclass and override this method if they want to store + * this state in a different location. + * + * @param string $state + * + * @throws FacebookSDKException + */ + protected function storeState($state) + { + if ($this->checkForSessionStatus === true + && session_status() !== PHP_SESSION_ACTIVE) { + throw new FacebookSDKException( + 'Session not active, could not store state.', 720 + ); + } + $_SESSION[$this->sessionPrefix . 'state'] = $state; + } + + /** + * Loads a state string from session storage for CSRF validation. May return + * null if no object exists. Developers should subclass and override this + * method if they want to load the state from a different location. + * + * @return string|null + * + * @throws FacebookSDKException + */ + protected function loadState() + { + + if ($this->checkForSessionStatus === true + && session_status() !== PHP_SESSION_ACTIVE) { + throw new FacebookSDKException( + 'Session not active, could not load state.', 721 + ); + } + if (isset($_SESSION[$this->sessionPrefix . 'state'])) { + $this->state = $_SESSION[$this->sessionPrefix . 'state']; + return $this->state; + } + return null; + } + + /** + * Generate a cryptographically secure pseudrandom number + * + * @param integer $bytes - number of bytes to return + * + * @return string + * + * @throws FacebookSDKException + * + * @todo Support Windows platforms + */ + public function random($bytes) + { + if (!is_numeric($bytes)) { + throw new FacebookSDKException( + "random() expects an integer" + ); + } + if ($bytes < 1) { + throw new FacebookSDKException( + "random() expects an integer greater than zero" + ); + } + $buf = ''; + // http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers/ + if (!ini_get('open_basedir') + && is_readable('/dev/urandom')) { + $fp = fopen('/dev/urandom', 'rb'); + if ($fp !== FALSE) { + $buf = fread($fp, $bytes); + fclose($fp); + if($buf !== FALSE) { + return bin2hex($buf); + } + } + } + + if (function_exists('mcrypt_create_iv')) { + $buf = mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM); + if ($buf !== FALSE) { + return bin2hex($buf); + } + } + + while (strlen($buf) < $bytes) { + $buf .= md5(uniqid(mt_rand(), true), true); + // We are appending raw binary + } + return bin2hex(substr($buf, 0, $bytes)); + } + + /** + * Disables the session_status() check when using $_SESSION + */ + public function disableSessionStatusCheck() + { + $this->checkForSessionStatus = false; + } + +} diff --git a/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookRequest.php b/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookRequest.php new file mode 100644 index 0000000000..5660f3d947 --- /dev/null +++ b/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookRequest.php @@ -0,0 +1,313 @@ + + * @author David Poll + */ +class FacebookRequest +{ + + /** + * @const string Version number of the Facebook PHP SDK. + */ + const VERSION = '4.0.15'; + + /** + * @const string Default Graph API version for requests + */ + const GRAPH_API_VERSION = 'v2.2'; + + /** + * @const string Graph API URL + */ + const BASE_GRAPH_URL = 'https://graph.facebook.com'; + + /** + * @var FacebookSession The session used for this request + */ + private $session; + + /** + * @var string The HTTP method for the request + */ + private $method; + + /** + * @var string The path for the request + */ + private $path; + + /** + * @var array The parameters for the request + */ + private $params; + + /** + * @var string The Graph API version for the request + */ + private $version; + + /** + * @var string ETag sent with the request + */ + private $etag; + + /** + * @var FacebookHttpable HTTP client handler + */ + private static $httpClientHandler; + + /** + * @var int The number of calls that have been made to Graph. + */ + public static $requestCount = 0; + + /** + * getSession - Returns the associated FacebookSession. + * + * @return FacebookSession + */ + public function getSession() + { + return $this->session; + } + + /** + * getPath - Returns the associated path. + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * getParameters - Returns the associated parameters. + * + * @return array + */ + public function getParameters() + { + return $this->params; + } + + /** + * getMethod - Returns the associated method. + * + * @return string + */ + public function getMethod() + { + return $this->method; + } + + /** + * getETag - Returns the ETag sent with the request. + * + * @return string + */ + public function getETag() + { + return $this->etag; + } + + /** + * setHttpClientHandler - Returns an instance of the HTTP client + * handler + * + * @param \Facebook\HttpClients\FacebookHttpable + */ + public static function setHttpClientHandler(FacebookHttpable $handler) + { + static::$httpClientHandler = $handler; + } + + /** + * getHttpClientHandler - Returns an instance of the HTTP client + * data handler + * + * @return FacebookHttpable + */ + public static function getHttpClientHandler() + { + if (static::$httpClientHandler) { + return static::$httpClientHandler; + } + return function_exists('curl_init') ? new FacebookCurlHttpClient() : new FacebookStreamHttpClient(); + } + + /** + * FacebookRequest - Returns a new request using the given session. optional + * parameters hash will be sent with the request. This object is + * immutable. + * + * @param FacebookSession $session + * @param string $method + * @param string $path + * @param array|null $parameters + * @param string|null $version + * @param string|null $etag + */ + public function __construct( + FacebookSession $session, $method, $path, $parameters = null, $version = null, $etag = null + ) + { + $this->session = $session; + $this->method = $method; + $this->path = $path; + if ($version) { + $this->version = $version; + } else { + $this->version = static::GRAPH_API_VERSION; + } + $this->etag = $etag; + + $params = ($parameters ?: array()); + if ($session + && !isset($params["access_token"])) { + $params["access_token"] = $session->getToken(); + } + if (FacebookSession::useAppSecretProof() + && !isset($params["appsecret_proof"])) { + $params["appsecret_proof"] = $this->getAppSecretProof( + $params["access_token"] + ); + } + $this->params = $params; + } + + /** + * Returns the base Graph URL. + * + * @return string + */ + protected function getRequestURL() + { + return static::BASE_GRAPH_URL . '/' . $this->version . $this->path; + } + + /** + * execute - Makes the request to Facebook and returns the result. + * + * @return FacebookResponse + * + * @throws FacebookSDKException + * @throws FacebookRequestException + */ + public function execute() + { + $url = $this->getRequestURL(); + $params = $this->getParameters(); + + if ($this->method === "GET") { + $url = self::appendParamsToUrl($url, $params); + $params = array(); + } + + $connection = self::getHttpClientHandler(); + $connection->addRequestHeader('User-Agent', 'fb-php-' . self::VERSION); + $connection->addRequestHeader('Accept-Encoding', '*'); // Support all available encodings. + + // ETag + if (isset($this->etag)) { + $connection->addRequestHeader('If-None-Match', $this->etag); + } + + // Should throw `FacebookSDKException` exception on HTTP client error. + // Don't catch to allow it to bubble up. + $result = $connection->send($url, $this->method, $params); + + static::$requestCount++; + + $etagHit = 304 == $connection->getResponseHttpStatusCode(); + + $headers = $connection->getResponseHeaders(); + $etagReceived = isset($headers['ETag']) ? $headers['ETag'] : null; + + $decodedResult = json_decode($result); + if ($decodedResult === null) { + $out = array(); + parse_str($result, $out); + return new FacebookResponse($this, $out, $result, $etagHit, $etagReceived); + } + if (isset($decodedResult->error)) { + throw FacebookRequestException::create( + $result, + $decodedResult->error, + $connection->getResponseHttpStatusCode() + ); + } + + return new FacebookResponse($this, $decodedResult, $result, $etagHit, $etagReceived); + } + + /** + * Generate and return the appsecret_proof value for an access_token + * + * @param string $token + * + * @return string + */ + public function getAppSecretProof($token) + { + return hash_hmac('sha256', $token, FacebookSession::_getTargetAppSecret()); + } + + /** + * appendParamsToUrl - Gracefully appends params to the URL. + * + * @param string $url + * @param array $params + * + * @return string + */ + public static function appendParamsToUrl($url, $params = array()) + { + if (!$params) { + return $url; + } + + if (strpos($url, '?') === false) { + return $url . '?' . http_build_query($params, null, '&'); + } + + list($path, $query_string) = explode('?', $url, 2); + parse_str($query_string, $query_array); + + // Favor params from the original URL over $params + $params = array_merge($params, $query_array); + + return $path . '?' . http_build_query($params, null, '&'); + } + +} diff --git a/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookRequestException.php b/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookRequestException.php new file mode 100644 index 0000000000..8d3fe7167b --- /dev/null +++ b/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookRequestException.php @@ -0,0 +1,222 @@ + + * @author David Poll + */ +class FacebookRequestException extends FacebookSDKException +{ + + /** + * @var int Status code for the response causing the exception + */ + private $statusCode; + + /** + * @var string Raw response + */ + private $rawResponse; + + /** + * @var array Decoded response + */ + private $responseData; + + /** + * Creates a FacebookRequestException. + * + * @param string $rawResponse The raw response from the Graph API + * @param array $responseData The decoded response from the Graph API + * @param int $statusCode + */ + public function __construct($rawResponse, $responseData, $statusCode) + { + $this->rawResponse = $rawResponse; + $this->statusCode = $statusCode; + $this->responseData = self::convertToArray($responseData); + parent::__construct( + $this->get('message', 'Unknown Exception'), $this->get('code', -1), null + ); + } + + /** + * Process an error payload from the Graph API and return the appropriate + * exception subclass. + * + * @param string $raw the raw response from the Graph API + * @param array $data the decoded response from the Graph API + * @param int $statusCode the HTTP response code + * + * @return FacebookRequestException + */ + public static function create($raw, $data, $statusCode) + { + $data = self::convertToArray($data); + if (!isset($data['error']['code']) && isset($data['code'])) { + $data = array('error' => $data); + } + $code = (isset($data['error']['code']) ? $data['error']['code'] : null); + + if (isset($data['error']['error_subcode'])) { + switch ($data['error']['error_subcode']) { + // Other authentication issues + case 458: + case 459: + case 460: + case 463: + case 464: + case 467: + return new FacebookAuthorizationException($raw, $data, $statusCode); + break; + } + } + + switch ($code) { + // Login status or token expired, revoked, or invalid + case 100: + case 102: + case 190: + return new FacebookAuthorizationException($raw, $data, $statusCode); + break; + + // Server issue, possible downtime + case 1: + case 2: + return new FacebookServerException($raw, $data, $statusCode); + break; + + // API Throttling + case 4: + case 17: + case 341: + return new FacebookThrottleException($raw, $data, $statusCode); + break; + + // Duplicate Post + case 506: + return new FacebookClientException($raw, $data, $statusCode); + break; + } + + // Missing Permissions + if ($code == 10 || ($code >= 200 && $code <= 299)) { + return new FacebookPermissionException($raw, $data, $statusCode); + } + + // OAuth authentication error + if (isset($data['error']['type']) + and $data['error']['type'] === 'OAuthException') { + return new FacebookAuthorizationException($raw, $data, $statusCode); + } + + // All others + return new FacebookOtherException($raw, $data, $statusCode); + } + + /** + * Checks isset and returns that or a default value. + * + * @param string $key + * @param mixed $default + * + * @return mixed + */ + private function get($key, $default = null) + { + if (isset($this->responseData['error'][$key])) { + return $this->responseData['error'][$key]; + } + return $default; + } + + /** + * Returns the HTTP status code + * + * @return int + */ + public function getHttpStatusCode() + { + return $this->statusCode; + } + + /** + * Returns the sub-error code + * + * @return int + */ + public function getSubErrorCode() + { + return $this->get('error_subcode', -1); + } + + /** + * Returns the error type + * + * @return string + */ + public function getErrorType() + { + return $this->get('type', ''); + } + + /** + * Returns the raw response used to create the exception. + * + * @return string + */ + public function getRawResponse() + { + return $this->rawResponse; + } + + /** + * Returns the decoded response used to create the exception. + * + * @return array + */ + public function getResponse() + { + return $this->responseData; + } + + /** + * Converts a stdClass object to an array + * + * @param mixed $object + * + * @return array + */ + private static function convertToArray($object) + { + if ($object instanceof \stdClass) { + return get_object_vars($object); + } + return $object; + } + +} \ No newline at end of file diff --git a/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookResponse.php b/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookResponse.php new file mode 100644 index 0000000000..622cef49ec --- /dev/null +++ b/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookResponse.php @@ -0,0 +1,206 @@ + + * @author David Poll + */ +class FacebookResponse +{ + + /** + * @var FacebookRequest The request which produced this response + */ + private $request; + + /** + * @var array The decoded response from the Graph API + */ + private $responseData; + + /** + * @var string The raw response from the Graph API + */ + private $rawResponse; + + /** + * @var bool Indicates whether sent ETag matched the one on the FB side + */ + private $etagHit; + + /** + * @var string ETag received with the response. `null` in case of ETag hit. + */ + private $etag; + + /** + * Creates a FacebookResponse object for a given request and response. + * + * @param FacebookRequest $request + * @param array $responseData JSON Decoded response data + * @param string $rawResponse Raw string response + * @param bool $etagHit Indicates whether sent ETag matched the one on the FB side + * @param string|null $etag ETag received with the response. `null` in case of ETag hit. + */ + public function __construct($request, $responseData, $rawResponse, $etagHit = false, $etag = null) + { + $this->request = $request; + $this->responseData = $responseData; + $this->rawResponse = $rawResponse; + $this->etagHit = $etagHit; + $this->etag = $etag; + } + + /** + * Returns the request which produced this response. + * + * @return FacebookRequest + */ + public function getRequest() + { + return $this->request; + } + + /** + * Returns the decoded response data. + * + * @return array + */ + public function getResponse() + { + return $this->responseData; + } + + /** + * Returns the raw response + * + * @return string + */ + public function getRawResponse() + { + return $this->rawResponse; + } + + /** + * Returns true if ETag matched the one sent with a request + * + * @return bool + */ + public function isETagHit() + { + return $this->etagHit; + } + + /** + * Returns the ETag + * + * @return string + */ + public function getETag() + { + return $this->etag; + } + + /** + * Gets the result as a GraphObject. If a type is specified, returns the + * strongly-typed subclass of GraphObject for the data. + * + * @param string $type + * + * @return mixed + */ + public function getGraphObject($type = 'Facebook\GraphObject') { + return (new GraphObject($this->responseData))->cast($type); + } + + /** + * Returns an array of GraphObject returned by the request. If a type is + * specified, returns the strongly-typed subclass of GraphObject for the data. + * + * @param string $type + * + * @return mixed + */ + public function getGraphObjectList($type = 'Facebook\GraphObject') { + $out = array(); + $data = $this->responseData->data; + $dataLength = count($data); + for ($i = 0; $i < $dataLength; $i++) { + $out[] = (new GraphObject($data[$i]))->cast($type); + } + return $out; + } + + /** + * If this response has paginated data, returns the FacebookRequest for the + * next page, or null. + * + * @return FacebookRequest|null + */ + public function getRequestForNextPage() + { + return $this->handlePagination('next'); + } + + /** + * If this response has paginated data, returns the FacebookRequest for the + * previous page, or null. + * + * @return FacebookRequest|null + */ + public function getRequestForPreviousPage() + { + return $this->handlePagination('previous'); + } + + /** + * Returns the FacebookRequest for the previous or next page, or null. + * + * @param string $direction + * + * @return FacebookRequest|null + */ + private function handlePagination($direction) { + if (isset($this->responseData->paging->$direction)) { + $url = parse_url($this->responseData->paging->$direction); + parse_str($url['query'], $params); + + if (isset($params['type']) && strpos($this->request->getPath(), $params['type']) !== false){ + unset($params['type']); + } + return new FacebookRequest( + $this->request->getSession(), + $this->request->getMethod(), + $this->request->getPath(), + $params + ); + } else { + return null; + } + } + +} diff --git a/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookSDKException.php b/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookSDKException.php new file mode 100644 index 0000000000..92d1412db8 --- /dev/null +++ b/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookSDKException.php @@ -0,0 +1,33 @@ + + * @author David Poll + */ +class FacebookSession +{ + + /** + * @var string + */ + private static $defaultAppId; + + /** + * @var string + */ + private static $defaultAppSecret; + + /** + * @var AccessToken The AccessToken entity for this connection. + */ + private $accessToken; + + /** + * @var SignedRequest + */ + private $signedRequest; + + /** + * @var bool + */ + protected static $useAppSecretProof = true; + + /** + * When creating a Session from an access_token, use: + * var $session = new FacebookSession($accessToken); + * This will validate the token and provide a Session object ready for use. + * It will throw a SessionException in case of error. + * + * @param AccessToken|string $accessToken + * @param SignedRequest $signedRequest The SignedRequest entity + */ + public function __construct($accessToken, SignedRequest $signedRequest = null) + { + $this->accessToken = $accessToken instanceof AccessToken ? $accessToken : new AccessToken($accessToken); + $this->signedRequest = $signedRequest; + } + + /** + * Returns the access token. + * + * @return string + */ + public function getToken() + { + return (string) $this->accessToken; + } + + /** + * Returns the access token entity. + * + * @return AccessToken + */ + public function getAccessToken() + { + return $this->accessToken; + } + + /** + * Returns the SignedRequest entity. + * + * @return SignedRequest + */ + public function getSignedRequest() + { + return $this->signedRequest; + } + + /** + * Returns the signed request payload. + * + * @return null|array + */ + public function getSignedRequestData() + { + return $this->signedRequest ? $this->signedRequest->getPayload() : null; + } + + /** + * Returns a property from the signed request data if available. + * + * @param string $key + * + * @return null|mixed + */ + public function getSignedRequestProperty($key) + { + return $this->signedRequest ? $this->signedRequest->get($key) : null; + } + + /** + * Returns user_id from signed request data if available. + * + * @return null|string + */ + public function getUserId() + { + return $this->signedRequest ? $this->signedRequest->getUserId() : null; + } + + // @TODO Remove getSessionInfo() in 4.1: can be accessed from AccessToken directly + /** + * getSessionInfo - Makes a request to /debug_token with the appropriate + * arguments to get debug information about the sessions token. + * + * @param string|null $appId + * @param string|null $appSecret + * + * @return GraphSessionInfo + */ + public function getSessionInfo($appId = null, $appSecret = null) + { + return $this->accessToken->getInfo($appId, $appSecret); + } + + // @TODO Remove getLongLivedSession() in 4.1: can be accessed from AccessToken directly + /** + * getLongLivedSession - Returns a new Facebook session resulting from + * extending a short-lived access token. If this session is not + * short-lived, returns $this. + * + * @param string|null $appId + * @param string|null $appSecret + * + * @return FacebookSession + */ + public function getLongLivedSession($appId = null, $appSecret = null) + { + $longLivedAccessToken = $this->accessToken->extend($appId, $appSecret); + return new static($longLivedAccessToken, $this->signedRequest); + } + + // @TODO Remove getExchangeToken() in 4.1: can be accessed from AccessToken directly + /** + * getExchangeToken - Returns an exchange token string which can be sent + * back to clients and exchanged for a device-linked access token. + * + * @param string|null $appId + * @param string|null $appSecret + * + * @return string + */ + public function getExchangeToken($appId = null, $appSecret = null) + { + return AccessToken::getCodeFromAccessToken($this->accessToken, $appId, $appSecret); + } + + // @TODO Remove validate() in 4.1: can be accessed from AccessToken directly + /** + * validate - Ensures the current session is valid, throwing an exception if + * not. Fetches token info from Facebook. + * + * @param string|null $appId Application ID to use + * @param string|null $appSecret App secret value to use + * @param string|null $machineId + * + * @return boolean + * + * @throws FacebookSDKException + */ + public function validate($appId = null, $appSecret = null, $machineId = null) + { + if ($this->accessToken->isValid($appId, $appSecret, $machineId)) { + return true; + } + + // @TODO For v4.1 this should not throw an exception, but just return false. + throw new FacebookSDKException( + 'Session has expired, or is not valid for this app.', 601 + ); + } + + // @TODO Remove validateSessionInfo() in 4.1: can be accessed from AccessToken directly + /** + * validateTokenInfo - Ensures the provided GraphSessionInfo object is valid, + * throwing an exception if not. Ensures the appId matches, + * that the token is valid and has not expired. + * + * @param GraphSessionInfo $tokenInfo + * @param string|null $appId Application ID to use + * @param string|null $machineId + * + * @return boolean + * + * @throws FacebookSDKException + */ + public static function validateSessionInfo(GraphSessionInfo $tokenInfo, + $appId = null, + $machineId = null) + { + if (AccessToken::validateAccessToken($tokenInfo, $appId, $machineId)) { + return true; + } + + // @TODO For v4.1 this should not throw an exception, but just return false. + throw new FacebookSDKException( + 'Session has expired, or is not valid for this app.', 601 + ); + } + + /** + * newSessionFromSignedRequest - Returns a FacebookSession for a + * given signed request. + * + * @param SignedRequest $signedRequest + * + * @return FacebookSession + */ + public static function newSessionFromSignedRequest(SignedRequest $signedRequest) + { + if ($signedRequest->get('code') + && !$signedRequest->get('oauth_token')) { + return self::newSessionAfterValidation($signedRequest); + } + $accessToken = $signedRequest->get('oauth_token'); + $expiresAt = $signedRequest->get('expires', 0); + $accessToken = new AccessToken($accessToken, $expiresAt); + return new static($accessToken, $signedRequest); + } + + /** + * newSessionAfterValidation - Returns a FacebookSession for a + * validated & parsed signed request. + * + * @param SignedRequest $signedRequest + * + * @return FacebookSession + */ + protected static function newSessionAfterValidation(SignedRequest $signedRequest) + { + $code = $signedRequest->get('code'); + $accessToken = AccessToken::getAccessTokenFromCode($code); + return new static($accessToken, $signedRequest); + } + + /** + * newAppSession - Returns a FacebookSession configured with a token for the + * application which can be used for publishing and requesting app-level + * information. + * + * @param string|null $appId Application ID to use + * @param string|null $appSecret App secret value to use + * + * @return FacebookSession + */ + public static function newAppSession($appId = null, $appSecret = null) + { + $targetAppId = static::_getTargetAppId($appId); + $targetAppSecret = static::_getTargetAppSecret($appSecret); + return new FacebookSession( + $targetAppId . '|' . $targetAppSecret + ); + } + + /** + * setDefaultApplication - Will set the static default appId and appSecret + * to be used for API requests. + * + * @param string $appId Application ID to use by default + * @param string $appSecret App secret value to use by default + */ + public static function setDefaultApplication($appId, $appSecret) + { + self::$defaultAppId = $appId; + self::$defaultAppSecret = $appSecret; + } + + /** + * _getTargetAppId - Will return either the provided app Id or the default, + * throwing if neither are populated. + * + * @param string $appId + * + * @return string + * + * @throws FacebookSDKException + */ + public static function _getTargetAppId($appId = null) { + $target = ($appId ?: self::$defaultAppId); + if (!$target) { + throw new FacebookSDKException( + 'You must provide or set a default application id.', 700 + ); + } + return $target; + } + + /** + * _getTargetAppSecret - Will return either the provided app secret or the + * default, throwing if neither are populated. + * + * @param string $appSecret + * + * @return string + * + * @throws FacebookSDKException + */ + public static function _getTargetAppSecret($appSecret = null) { + $target = ($appSecret ?: self::$defaultAppSecret); + if (!$target) { + throw new FacebookSDKException( + 'You must provide or set a default application secret.', 701 + ); + } + return $target; + } + + /** + * Enable or disable sending the appsecret_proof with requests. + * + * @param bool $on + */ + public static function enableAppSecretProof($on = true) + { + static::$useAppSecretProof = ($on ? true : false); + } + + /** + * Get whether or not appsecret_proof should be sent with requests. + * + * @return bool + */ + public static function useAppSecretProof() + { + return static::$useAppSecretProof; + } + +} diff --git a/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookSignedRequestFromInputHelper.php b/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookSignedRequestFromInputHelper.php new file mode 100644 index 0000000000..c497246a2f --- /dev/null +++ b/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookSignedRequestFromInputHelper.php @@ -0,0 +1,166 @@ +appId = FacebookSession::_getTargetAppId($appId); + $this->appSecret = FacebookSession::_getTargetAppSecret($appSecret); + + $this->instantiateSignedRequest(); + } + + /** + * Instantiates a new SignedRequest entity. + * + * @param string|null + */ + public function instantiateSignedRequest($rawSignedRequest = null) + { + $rawSignedRequest = $rawSignedRequest ?: $this->getRawSignedRequest(); + + if (!$rawSignedRequest) { + return; + } + + $this->signedRequest = new SignedRequest($rawSignedRequest, $this->state, $this->appSecret); + } + + /** + * Instantiates a FacebookSession from the signed request from input. + * + * @return FacebookSession|null + */ + public function getSession() + { + if ($this->signedRequest && $this->signedRequest->hasOAuthData()) { + return FacebookSession::newSessionFromSignedRequest($this->signedRequest); + } + return null; + } + + /** + * Returns the SignedRequest entity. + * + * @return \Facebook\Entities\SignedRequest|null + */ + public function getSignedRequest() + { + return $this->signedRequest; + } + + /** + * Returns the user_id if available. + * + * @return string|null + */ + public function getUserId() + { + return $this->signedRequest ? $this->signedRequest->getUserId() : null; + } + + /** + * Get raw signed request from input. + * + * @return string|null + */ + abstract public function getRawSignedRequest(); + + /** + * Get raw signed request from GET input. + * + * @return string|null + */ + public function getRawSignedRequestFromGet() + { + if (isset($_GET['signed_request'])) { + return $_GET['signed_request']; + } + + return null; + } + + /** + * Get raw signed request from POST input. + * + * @return string|null + */ + public function getRawSignedRequestFromPost() + { + if (isset($_POST['signed_request'])) { + return $_POST['signed_request']; + } + + return null; + } + + /** + * Get raw signed request from cookie set from the Javascript SDK. + * + * @return string|null + */ + public function getRawSignedRequestFromCookie() + { + if (isset($_COOKIE['fbsr_' . $this->appId])) { + return $_COOKIE['fbsr_' . $this->appId]; + } + return null; + } + +} diff --git a/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookThrottleException.php b/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookThrottleException.php new file mode 100644 index 0000000000..a1b2f09964 --- /dev/null +++ b/main/auth/external_login/facebook-php-sdk/src/Facebook/FacebookThrottleException.php @@ -0,0 +1,33 @@ + + */ + +class GraphAlbum extends GraphObject +{ + /** + * Returns the ID for the album. + * + * @return string|null + */ + public function getId() + { + return $this->getProperty('id'); + } + + /** + * Returns whether the viewer can upload photos to this album. + * + * @return boolean|null + */ + public function canUpload() + { + return $this->getProperty('can_upload'); + } + + /** + * Returns the number of photos in this album. + * + * @return int|null + */ + public function getCount() + { + return $this->getProperty('count'); + } + + /** + * Returns the ID of the album's cover photo. + * + * @return string|null + */ + public function getCoverPhoto() + { + return $this->getProperty('cover_photo'); + } + + /** + * Returns the time the album was initially created. + * + * @return \DateTime|null + */ + public function getCreatedTime() + { + $value = $this->getProperty('created_time'); + if ($value) { + return new \DateTime($value); + } + return null; + } + + /** + * Returns the time the album was updated. + * + * @return \DateTime|null + */ + public function getUpdatedTime() + { + $value = $this->getProperty('updated_time'); + if ($value) { + return new \DateTime($value); + } + return null; + } + + /** + * Returns the description of the album. + * + * @return string|null + */ + public function getDescription() + { + return $this->getProperty('description'); + } + + /** + * Returns profile that created the album. + * + * @return GraphUser|null + */ + public function getFrom() + { + return $this->getProperty('from', GraphUser::className()); + } + + /** + * Returns a link to this album on Facebook. + * + * @return string|null + */ + public function getLink() + { + return $this->getProperty('link'); + } + + /** + * Returns the textual location of the album. + * + * @return string|null + */ + public function getLocation() + { + return $this->getProperty('location'); + } + + /** + * Returns the title of the album. + * + * @return string|null + */ + public function getName() + { + return $this->getProperty('name'); + } + + /** + * Returns the privacy settings for the album. + * + * @return string|null + */ + public function getPrivacy() + { + return $this->getProperty('privacy'); + } + + /** + * Returns the type of the album. enum{profile, mobile, wall, normal, album} + * + * @return string|null + */ + public function getType() + { + return $this->getProperty('type'); + } + + //TODO: public function getPlace() that should return GraphPage +} diff --git a/main/auth/external_login/facebook-php-sdk/src/Facebook/GraphLocation.php b/main/auth/external_login/facebook-php-sdk/src/Facebook/GraphLocation.php new file mode 100644 index 0000000000..5326ea53ac --- /dev/null +++ b/main/auth/external_login/facebook-php-sdk/src/Facebook/GraphLocation.php @@ -0,0 +1,105 @@ + + * @author David Poll + */ +class GraphLocation extends GraphObject +{ + + /** + * Returns the street component of the location + * + * @return string|null + */ + public function getStreet() + { + return $this->getProperty('street'); + } + + /** + * Returns the city component of the location + * + * @return string|null + */ + public function getCity() + { + return $this->getProperty('city'); + } + + /** + * Returns the state component of the location + * + * @return string|null + */ + public function getState() + { + return $this->getProperty('state'); + } + + /** + * Returns the country component of the location + * + * @return string|null + */ + public function getCountry() + { + return $this->getProperty('country'); + } + + /** + * Returns the zipcode component of the location + * + * @return string|null + */ + public function getZip() + { + return $this->getProperty('zip'); + } + + /** + * Returns the latitude component of the location + * + * @return float|null + */ + public function getLatitude() + { + return $this->getProperty('latitude'); + } + + /** + * Returns the street component of the location + * + * @return float|null + */ + public function getLongitude() + { + return $this->getProperty('longitude'); + } + +} \ No newline at end of file diff --git a/main/auth/external_login/facebook-php-sdk/src/Facebook/GraphObject.php b/main/auth/external_login/facebook-php-sdk/src/Facebook/GraphObject.php new file mode 100644 index 0000000000..3c4b84b2b4 --- /dev/null +++ b/main/auth/external_login/facebook-php-sdk/src/Facebook/GraphObject.php @@ -0,0 +1,171 @@ + + * @author David Poll + */ +class GraphObject +{ + + /** + * @var array - Holds the raw associative data for this object + */ + protected $backingData; + + /** + * Creates a GraphObject using the data provided. + * + * @param array $raw + */ + public function __construct($raw) + { + if ($raw instanceof \stdClass) { + $raw = get_object_vars($raw); + } + $this->backingData = $raw; + + if (isset($this->backingData['data']) && count($this->backingData) === 1) { + if ($this->backingData['data'] instanceof \stdClass) { + $this->backingData = get_object_vars($this->backingData['data']); + } else { + $this->backingData = $this->backingData['data']; + } + } + } + + /** + * cast - Return a new instance of a FacebookGraphObject subclass for this + * objects underlying data. + * + * @param string $type The GraphObject subclass to cast to + * + * @return GraphObject + * + * @throws FacebookSDKException + */ + public function cast($type) + { + if ($this instanceof $type) { + return $this; + } + if (is_subclass_of($type, GraphObject::className())) { + return new $type($this->backingData); + } else { + throw new FacebookSDKException( + 'Cannot cast to an object that is not a GraphObject subclass', 620 + ); + } + } + + /** + * asArray - Return a key-value associative array for the given graph object. + * + * @return array + */ + public function asArray() + { + return $this->backingData; + } + + /** + * getProperty - Gets the value of the named property for this graph object, + * cast to the appropriate subclass type if provided. + * + * @param string $name The property to retrieve + * @param string $type The subclass of GraphObject, optionally + * + * @return mixed + */ + public function getProperty($name, $type = 'Facebook\GraphObject') + { + if (isset($this->backingData[$name])) { + $value = $this->backingData[$name]; + if (is_scalar($value)) { + return $value; + } else { + return (new GraphObject($value))->cast($type); + } + } else { + return null; + } + } + + /** + * getPropertyAsArray - Get the list value of a named property for this graph + * object, where each item has been cast to the appropriate subclass type + * if provided. + * + * Calling this for a property that is not an array, the behavior + * is undefined, so don’t do this. + * + * @param string $name The property to retrieve + * @param string $type The subclass of GraphObject, optionally + * + * @return array + */ + public function getPropertyAsArray($name, $type = 'Facebook\GraphObject') + { + $target = array(); + if (isset($this->backingData[$name]['data'])) { + $target = $this->backingData[$name]['data']; + } else if (isset($this->backingData[$name]) + && !is_scalar($this->backingData[$name])) { + $target = $this->backingData[$name]; + } + $out = array(); + foreach ($target as $key => $value) { + if (is_scalar($value)) { + $out[$key] = $value; + } else { + $out[$key] = (new GraphObject($value))->cast($type); + } + } + return $out; + } + + /** + * getPropertyNames - Returns a list of all properties set on the object. + * + * @return array + */ + public function getPropertyNames() + { + return array_keys($this->backingData); + } + + /** + * Returns the string class name of the GraphObject or subclass. + * + * @return string + */ + public static function className() + { + return get_called_class(); + } + +} \ No newline at end of file diff --git a/main/auth/external_login/facebook-php-sdk/src/Facebook/GraphPage.php b/main/auth/external_login/facebook-php-sdk/src/Facebook/GraphPage.php new file mode 100644 index 0000000000..a66068e99b --- /dev/null +++ b/main/auth/external_login/facebook-php-sdk/src/Facebook/GraphPage.php @@ -0,0 +1,64 @@ + + */ +class GraphPage extends GraphObject +{ + + /** + * Returns the ID for the user's page as a string if present. + * + * @return string|null + */ + public function getId() + { + return $this->getProperty('id'); + } + + /** + * Returns the Category for the user's page as a string if present. + * + * @return string|null + */ + public function getCategory() + { + return $this->getProperty('category'); + } + + /** + * Returns the Name of the user's page as a string if present. + * + * @return string|null + */ + public function getName() + { + return $this->getProperty('name'); + } + +} \ No newline at end of file diff --git a/main/auth/external_login/facebook-php-sdk/src/Facebook/GraphSessionInfo.php b/main/auth/external_login/facebook-php-sdk/src/Facebook/GraphSessionInfo.php new file mode 100644 index 0000000000..4b97580aef --- /dev/null +++ b/main/auth/external_login/facebook-php-sdk/src/Facebook/GraphSessionInfo.php @@ -0,0 +1,115 @@ + + * @author David Poll + */ +class GraphSessionInfo extends GraphObject +{ + + /** + * Returns the application id the token was issued for. + * + * @return string|null + */ + public function getAppId() + { + return $this->getProperty('app_id'); + } + + /** + * Returns the application name the token was issued for. + * + * @return string|null + */ + public function getApplication() + { + return $this->getProperty('application'); + } + + /** + * Returns the date & time that the token expires. + * + * @return \DateTime|null + */ + public function getExpiresAt() + { + $stamp = $this->getProperty('expires_at'); + if ($stamp) { + return (new \DateTime())->setTimestamp($stamp); + } else { + return null; + } + } + + /** + * Returns whether the token is valid. + * + * @return boolean + */ + public function isValid() + { + return $this->getProperty('is_valid'); + } + + /** + * Returns the date & time the token was issued at. + * + * @return \DateTime|null + */ + public function getIssuedAt() + { + $stamp = $this->getProperty('issued_at'); + if ($stamp) { + return (new \DateTime())->setTimestamp($stamp); + } else { + return null; + } + } + + /** + * Returns the scope permissions associated with the token. + * + * @return array + */ + public function getScopes() + { + return $this->getPropertyAsArray('scopes'); + } + + /** + * Returns the login id of the user associated with the token. + * + * @return string|null + */ + public function getId() + { + return $this->getProperty('user_id'); + } + +} \ No newline at end of file diff --git a/main/auth/external_login/facebook-php-sdk/src/Facebook/GraphUser.php b/main/auth/external_login/facebook-php-sdk/src/Facebook/GraphUser.php new file mode 100644 index 0000000000..9a9c5edbf5 --- /dev/null +++ b/main/auth/external_login/facebook-php-sdk/src/Facebook/GraphUser.php @@ -0,0 +1,135 @@ + + * @author David Poll + */ +class GraphUser extends GraphObject +{ + + /** + * Returns the ID for the user as a string if present. + * + * @return string|null + */ + public function getId() + { + return $this->getProperty('id'); + } + + /** + * Returns the name for the user as a string if present. + * + * @return string|null + */ + public function getName() + { + return $this->getProperty('name'); + } + + public function getEmail() + { + return $this->getProperty('email'); + } + + /** + * Returns the first name for the user as a string if present. + * + * @return string|null + */ + public function getFirstName() + { + return $this->getProperty('first_name'); + } + + /** + * Returns the middle name for the user as a string if present. + * + * @return string|null + */ + public function getMiddleName() + { + return $this->getProperty('middle_name'); + } + + /** + * Returns the last name for the user as a string if present. + * + * @return string|null + */ + public function getLastName() + { + return $this->getProperty('last_name'); + } + + /** + * Returns the gender for the user as a string if present. + * + * @return string|null + */ + public function getGender() + { + return $this->getProperty('gender'); + } + + /** + * Returns the Facebook URL for the user as a string if available. + * + * @return string|null + */ + public function getLink() + { + return $this->getProperty('link'); + } + + /** + * Returns the users birthday, if available. + * + * @return \DateTime|null + */ + public function getBirthday() + { + $value = $this->getProperty('birthday'); + if ($value) { + return new \DateTime($value); + } + return null; + } + + /** + * Returns the current location of the user as a FacebookGraphLocation + * if available. + * + * @return GraphLocation|null + */ + public function getLocation() + { + return $this->getProperty('location', GraphLocation::className()); + } + +} diff --git a/main/auth/external_login/facebook-php-sdk/src/Facebook/GraphUserPage.php b/main/auth/external_login/facebook-php-sdk/src/Facebook/GraphUserPage.php new file mode 100644 index 0000000000..9115da1135 --- /dev/null +++ b/main/auth/external_login/facebook-php-sdk/src/Facebook/GraphUserPage.php @@ -0,0 +1,84 @@ + + */ +class GraphUserPage extends GraphObject +{ + + /** + * Returns the ID for the user's page as a string if present. + * + * @return string|null + */ + public function getId() + { + return $this->getProperty('id'); + } + + /** + * Returns the Category for the user's page as a string if present. + * + * @return string|null + */ + public function getCategory() + { + return $this->getProperty('category'); + } + + /** + * Returns the Name of the user's page as a string if present. + * + * @return string|null + */ + public function getName() + { + return $this->getProperty('name'); + } + + /** + * Returns the Access Token used to access the user's page as a string if present. + * + * @return string|null + */ + public function getAccessToken() + { + return $this->getProperty('access_token'); + } + + /** + * Returns the Permissions for the user's page as an array if present. + * + * @return array|null + */ + public function getPermissions() + { + return $this->getProperty('perms'); + } + +} \ No newline at end of file diff --git a/main/auth/external_login/facebook-php-sdk/src/Facebook/HttpClients/FacebookCurl.php b/main/auth/external_login/facebook-php-sdk/src/Facebook/HttpClients/FacebookCurl.php new file mode 100644 index 0000000000..5b20e92db2 --- /dev/null +++ b/main/auth/external_login/facebook-php-sdk/src/Facebook/HttpClients/FacebookCurl.php @@ -0,0 +1,129 @@ +curl = curl_init(); + } + + /** + * Set a curl option + * + * @param $key + * @param $value + */ + public function setopt($key, $value) + { + curl_setopt($this->curl, $key, $value); + } + + /** + * Set an array of options to a curl resource + * + * @param array $options + */ + public function setopt_array(array $options) + { + curl_setopt_array($this->curl, $options); + } + + /** + * Send a curl request + * + * @return mixed + */ + public function exec() + { + return curl_exec($this->curl); + } + + /** + * Return the curl error number + * + * @return int + */ + public function errno() + { + return curl_errno($this->curl); + } + + /** + * Return the curl error message + * + * @return string + */ + public function error() + { + return curl_error($this->curl); + } + + /** + * Get info from a curl reference + * + * @param $type + * + * @return mixed + */ + public function getinfo($type) + { + return curl_getinfo($this->curl, $type); + } + + /** + * Get the currently installed curl version + * + * @return array + */ + public function version() + { + return curl_version(); + } + + /** + * Close the resource connection to curl + */ + public function close() + { + curl_close($this->curl); + } + +} diff --git a/main/auth/external_login/facebook-php-sdk/src/Facebook/HttpClients/FacebookCurlHttpClient.php b/main/auth/external_login/facebook-php-sdk/src/Facebook/HttpClients/FacebookCurlHttpClient.php new file mode 100644 index 0000000000..3c4f0620ec --- /dev/null +++ b/main/auth/external_login/facebook-php-sdk/src/Facebook/HttpClients/FacebookCurlHttpClient.php @@ -0,0 +1,329 @@ +requestHeaders[$key] = $value; + } + + /** + * The headers returned in the response + * + * @return array + */ + public function getResponseHeaders() + { + return $this->responseHeaders; + } + + /** + * The HTTP status response code + * + * @return int + */ + public function getResponseHttpStatusCode() + { + return $this->responseHttpStatusCode; + } + + /** + * Sends a request to the server + * + * @param string $url The endpoint to send the request to + * @param string $method The request method + * @param array $parameters The key value pairs to be sent in the body + * + * @return string Raw response from the server + * + * @throws \Facebook\FacebookSDKException + */ + public function send($url, $method = 'GET', $parameters = array()) + { + $this->openConnection($url, $method, $parameters); + $this->tryToSendRequest(); + + if ($this->curlErrorCode) { + throw new FacebookSDKException($this->curlErrorMessage, $this->curlErrorCode); + } + + // Separate the raw headers from the raw body + list($rawHeaders, $rawBody) = $this->extractResponseHeadersAndBody(); + + $this->responseHeaders = self::headersToArray($rawHeaders); + + $this->closeConnection(); + + return $rawBody; + } + + /** + * Opens a new curl connection + * + * @param string $url The endpoint to send the request to + * @param string $method The request method + * @param array $parameters The key value pairs to be sent in the body + */ + public function openConnection($url, $method = 'GET', $parameters = array()) + { + $options = array( + CURLOPT_URL => $url, + CURLOPT_CONNECTTIMEOUT => 10, + CURLOPT_TIMEOUT => 60, + CURLOPT_RETURNTRANSFER => true, // Follow 301 redirects + CURLOPT_HEADER => true, // Enable header processing + CURLOPT_SSL_VERIFYHOST => 2, + CURLOPT_SSL_VERIFYPEER => true, + CURLOPT_CAINFO => __DIR__ . '/certs/DigiCertHighAssuranceEVRootCA.pem', + ); + + if ($method !== "GET") { + $options[CURLOPT_POSTFIELDS] = $parameters; + } + if ($method === 'DELETE' || $method === 'PUT') { + $options[CURLOPT_CUSTOMREQUEST] = $method; + } + + if (!empty($this->requestHeaders)) { + $options[CURLOPT_HTTPHEADER] = $this->compileRequestHeaders(); + } + + if (self::$disableIPv6) { + $options[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4; + } + + self::$facebookCurl->init(); + self::$facebookCurl->setopt_array($options); + } + + /** + * Closes an existing curl connection + */ + public function closeConnection() + { + self::$facebookCurl->close(); + } + + /** + * Try to send the request + */ + public function tryToSendRequest() + { + $this->sendRequest(); + $this->curlErrorMessage = self::$facebookCurl->error(); + $this->curlErrorCode = self::$facebookCurl->errno(); + $this->responseHttpStatusCode = self::$facebookCurl->getinfo(CURLINFO_HTTP_CODE); + } + + /** + * Send the request and get the raw response from curl + */ + public function sendRequest() + { + $this->rawResponse = self::$facebookCurl->exec(); + } + + /** + * Compiles the request headers into a curl-friendly format + * + * @return array + */ + public function compileRequestHeaders() + { + $return = array(); + + foreach ($this->requestHeaders as $key => $value) { + $return[] = $key . ': ' . $value; + } + + return $return; + } + + /** + * Extracts the headers and the body into a two-part array + * + * @return array + */ + public function extractResponseHeadersAndBody() + { + $headerSize = self::getHeaderSize(); + + $rawHeaders = mb_substr($this->rawResponse, 0, $headerSize); + $rawBody = mb_substr($this->rawResponse, $headerSize); + + return array(trim($rawHeaders), trim($rawBody)); + } + + /** + * Converts raw header responses into an array + * + * @param string $rawHeaders + * + * @return array + */ + public static function headersToArray($rawHeaders) + { + $headers = array(); + + // Normalize line breaks + $rawHeaders = str_replace("\r\n", "\n", $rawHeaders); + + // There will be multiple headers if a 301 was followed + // or a proxy was followed, etc + $headerCollection = explode("\n\n", trim($rawHeaders)); + // We just want the last response (at the end) + $rawHeader = array_pop($headerCollection); + + $headerComponents = explode("\n", $rawHeader); + foreach ($headerComponents as $line) { + if (strpos($line, ': ') === false) { + $headers['http_code'] = $line; + } else { + list ($key, $value) = explode(': ', $line); + $headers[$key] = $value; + } + } + + return $headers; + } + + /** + * Return proper header size + * + * @return integer + */ + private function getHeaderSize() + { + $headerSize = self::$facebookCurl->getinfo(CURLINFO_HEADER_SIZE); + // This corrects a Curl bug where header size does not account + // for additional Proxy headers. + if ( self::needsCurlProxyFix() ) { + // Additional way to calculate the request body size. + if (preg_match('/Content-Length: (\d+)/', $this->rawResponse, $m)) { + $headerSize = mb_strlen($this->rawResponse) - $m[1]; + } elseif (stripos($this->rawResponse, self::CONNECTION_ESTABLISHED) !== false) { + $headerSize += mb_strlen(self::CONNECTION_ESTABLISHED); + } + } + + return $headerSize; + } + + /** + * Detect versions of Curl which report incorrect header lengths when + * using Proxies. + * + * @return boolean + */ + private static function needsCurlProxyFix() + { + $ver = self::$facebookCurl->version(); + $version = $ver['version_number']; + + return $version < self::CURL_PROXY_QUIRK_VER; + } + +} diff --git a/main/auth/external_login/facebook-php-sdk/src/Facebook/HttpClients/FacebookGuzzleHttpClient.php b/main/auth/external_login/facebook-php-sdk/src/Facebook/HttpClients/FacebookGuzzleHttpClient.php new file mode 100644 index 0000000000..235b40163a --- /dev/null +++ b/main/auth/external_login/facebook-php-sdk/src/Facebook/HttpClients/FacebookGuzzleHttpClient.php @@ -0,0 +1,134 @@ +requestHeaders[$key] = $value; + } + + /** + * The headers returned in the response + * + * @return array + */ + public function getResponseHeaders() + { + return $this->responseHeaders; + } + + /** + * The HTTP status response code + * + * @return int + */ + public function getResponseHttpStatusCode() + { + return $this->responseHttpStatusCode; + } + + /** + * Sends a request to the server + * + * @param string $url The endpoint to send the request to + * @param string $method The request method + * @param array $parameters The key value pairs to be sent in the body + * + * @return string Raw response from the server + * + * @throws \Facebook\FacebookSDKException + */ + public function send($url, $method = 'GET', $parameters = array()) + { + $options = array(); + if ($parameters) { + $options = array('body' => $parameters); + } + + $options['verify'] = __DIR__ . '/certs/DigiCertHighAssuranceEVRootCA.pem'; + + $request = self::$guzzleClient->createRequest($method, $url, $options); + + foreach($this->requestHeaders as $k => $v) { + $request->setHeader($k, $v); + } + + try { + $rawResponse = self::$guzzleClient->send($request); + } catch (RequestException $e) { + if ($e->getPrevious() instanceof AdapterException) { + throw new FacebookSDKException($e->getMessage(), $e->getCode()); + } + $rawResponse = $e->getResponse(); + } + + $this->responseHttpStatusCode = $rawResponse->getStatusCode(); + $this->responseHeaders = $rawResponse->getHeaders(); + + return $rawResponse->getBody(); + } + +} diff --git a/main/auth/external_login/facebook-php-sdk/src/Facebook/HttpClients/FacebookHttpable.php b/main/auth/external_login/facebook-php-sdk/src/Facebook/HttpClients/FacebookHttpable.php new file mode 100644 index 0000000000..e3afaf392d --- /dev/null +++ b/main/auth/external_login/facebook-php-sdk/src/Facebook/HttpClients/FacebookHttpable.php @@ -0,0 +1,68 @@ +stream = stream_context_create($options); + } + + /** + * The response headers from the stream wrapper + * + * @return array|null + */ + public function getResponseHeaders() + { + return $this->responseHeaders; + } + + /** + * Send a stream wrapped request + * + * @param string $url + * + * @return mixed + */ + public function fileGetContents($url) + { + $rawResponse = file_get_contents($url, false, $this->stream); + $this->responseHeaders = $http_response_header; + return $rawResponse; + } + +} diff --git a/main/auth/external_login/facebook-php-sdk/src/Facebook/HttpClients/FacebookStreamHttpClient.php b/main/auth/external_login/facebook-php-sdk/src/Facebook/HttpClients/FacebookStreamHttpClient.php new file mode 100644 index 0000000000..3a9197a543 --- /dev/null +++ b/main/auth/external_login/facebook-php-sdk/src/Facebook/HttpClients/FacebookStreamHttpClient.php @@ -0,0 +1,190 @@ +requestHeaders[$key] = $value; + } + + /** + * The headers returned in the response + * + * @return array + */ + public function getResponseHeaders() + { + return $this->responseHeaders; + } + + /** + * The HTTP status response code + * + * @return int + */ + public function getResponseHttpStatusCode() + { + return $this->responseHttpStatusCode; + } + + /** + * Sends a request to the server + * + * @param string $url The endpoint to send the request to + * @param string $method The request method + * @param array $parameters The key value pairs to be sent in the body + * + * @return string Raw response from the server + * + * @throws \Facebook\FacebookSDKException + */ + public function send($url, $method = 'GET', $parameters = array()) + { + $options = array( + 'http' => array( + 'method' => $method, + 'timeout' => 60, + 'ignore_errors' => true + ), + 'ssl' => array( + 'verify_peer' => true, + 'verify_peer_name' => true, + 'allow_self_signed' => true, // All root certificates are self-signed + 'cafile' => __DIR__ . '/certs/DigiCertHighAssuranceEVRootCA.pem', + ), + ); + + if ($parameters) { + $options['http']['content'] = http_build_query($parameters, null, '&'); + + $this->addRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + } + + $options['http']['header'] = $this->compileHeader(); + + self::$facebookStream->streamContextCreate($options); + $rawResponse = self::$facebookStream->fileGetContents($url); + $rawHeaders = self::$facebookStream->getResponseHeaders(); + + if ($rawResponse === false || !$rawHeaders) { + throw new FacebookSDKException('Stream returned an empty response', 660); + } + + $this->responseHeaders = self::formatHeadersToArray($rawHeaders); + $this->responseHttpStatusCode = self::getStatusCodeFromHeader($this->responseHeaders['http_code']); + + return $rawResponse; + } + + /** + * Formats the headers for use in the stream wrapper + * + * @return string + */ + public function compileHeader() + { + $header = []; + foreach($this->requestHeaders as $k => $v) { + $header[] = $k . ': ' . $v; + } + + return implode("\r\n", $header); + } + + /** + * Converts array of headers returned from the wrapper into + * something standard + * + * @param array $rawHeaders + * + * @return array + */ + public static function formatHeadersToArray(array $rawHeaders) + { + $headers = array(); + + foreach ($rawHeaders as $line) { + if (strpos($line, ':') === false) { + $headers['http_code'] = $line; + } else { + list ($key, $value) = explode(': ', $line); + $headers[$key] = $value; + } + } + + return $headers; + } + + /** + * Pulls out the HTTP status code from a response header + * + * @param string $header + * + * @return int + */ + public static function getStatusCodeFromHeader($header) + { + preg_match('|HTTP/\d\.\d\s+(\d+)\s+.*|', $header, $match); + return (int) $match[1]; + } + +} diff --git a/main/auth/external_login/facebook-php-sdk/src/Facebook/HttpClients/certs/DigiCertHighAssuranceEVRootCA.pem b/main/auth/external_login/facebook-php-sdk/src/Facebook/HttpClients/certs/DigiCertHighAssuranceEVRootCA.pem new file mode 100644 index 0000000000..9e6810ab70 --- /dev/null +++ b/main/auth/external_login/facebook-php-sdk/src/Facebook/HttpClients/certs/DigiCertHighAssuranceEVRootCA.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug +RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm ++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW +PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM +xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB +Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 +hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg +EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA +FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec +nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z +eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF +hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 +Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep ++OkuE6N36B9K +-----END CERTIFICATE----- diff --git a/main/auth/external_login/facebook.inc.php b/main/auth/external_login/facebook.inc.php index 3f69323c29..38e1d48b59 100755 --- a/main/auth/external_login/facebook.inc.php +++ b/main/auth/external_login/facebook.inc.php @@ -1,46 +1,71 @@ getUser(); - if ($user) { - try { - //Gets facebook user info - $fu = $facebook->api('/me'); - $username = $fu['username']; - if (api_get_setting('login_is_email') == 'true' || empty($fu['username'])) { - $username = change_to_valid_chamilo_login($fu['email']); - } + */ +function facebookConnect() +{ + global $facebook_config; + global $helper; + + try { + $helper = new FacebookRedirectLoginHelper($facebook_config['return_url']); + $session = $helper->getSessionFromRedirect(); + // see if we have a session + if (isset($session)) { + // graph api request for user data + $request = new FacebookRequest($session, 'GET', '/me'); + $response = $request->execute(); + // get response + $graphObject = $response->getGraphObject(); + $username = changeToValidChamiloLogin($graphObject->getProperty('email')); + $email = $graphObject->getProperty('email'); + $locale = $graphObject->getProperty('locale'); + $language = getLanguageForFacebook($locale); + if(!$language)$language='pl_PL'; + //Checks if user already exists in chamilo $u = array( - 'firstname' => $fu['first_name'], - 'lastname' => $fu['last_name'], + 'firstname' => $graphObject->getProperty('first_name'), + 'lastname' => $graphObject->getProperty('last_name'), 'status' => STUDENT, - 'email' => $fu['email'], + 'email' => $graphObject->getProperty('email'), 'username' => $username, - 'language' => 'french', + 'language' => $language, 'password' => DEFAULT_PASSWORD, 'auth_source' => 'facebook', //'courses' => $user_info['courses'], @@ -49,9 +74,9 @@ function facebook_connect() { //'manager' => $user_info['manager'], 'extra' => array() ); - $cu = api_get_user_info_from_username($username); - $chamilo_uinfo = api_get_user_info_from_username($username); - if ($chamilo_uinfo === false) { + + $chamiloUinfo = api_get_user_info_from_email($email); + if ($chamiloUinfo === false) { //we have to create the user $chamilo_uid = external_add_user($u); if ($chamilo_uid !== false) { @@ -64,7 +89,7 @@ function facebook_connect() { return false; } } else {//User already exists, update info and login - $chamilo_uid = $chamilo_uinfo['user_id']; + $chamilo_uid = $chamiloUinfo['user_id']; $u['user_id'] = $chamilo_uid; external_update_user($u); $_user['user_id'] = $chamilo_uid; @@ -73,34 +98,50 @@ function facebook_connect() { header('Location:' . api_get_path(WEB_PATH)); exit(); } - } catch (FacebookApiException $e) { - echo '
' . htmlspecialchars(print_r($e, true)) . '
'; - $user = null; } + } catch( FacebookRequestException $ex ) { + echo $ex; + } catch( Exception $ex ) { + // When validation fails or other local issues } } /** * Get facebook login url for the platform - * */ -function facebook_get_login_url() { - global $facebook, $facebook_config; - - $login_url = $facebook->getLoginUrl( - array( - 'scope' => 'email,publish_stream', - 'redirect_uri' => $facebook_config['return_url'] - ) - ); - return $login_url; + * @return mixed + */ +function facebookGetLoginUrl() +{ + global $facebook_config; + $helper = new FacebookRedirectLoginHelper($facebook_config['return_url']); + $loginUrl = $helper->getLoginUrl(array( + 'scope' => 'email')); + return $loginUrl; } /** - * @input : a string - * @return : a string containing valid chamilo login characters * Chamilo login only use characters lettres, des chiffres et les signes _ . - - * */ -function change_to_valid_chamilo_login($in_txt) { + * return a string containing valid chamilo login characters + * @param $in_txt + * @return mixed + */ +function changeToValidChamiloLogin($in_txt) +{ return preg_replace("/[^a-zA-Z1-9_\-.]/", "_", $in_txt); - exit; +} + +/** + * Get user language + * @param string $language + * @return bool + */ +function getLanguageForFacebook($language = 'en_US') +{ + $language = substr($language, 0, 2); + $sqlResult = Database::query("SELECT english_name FROM ".Database::get_main_table(TABLE_MAIN_LANGUAGE)." WHERE available = 1 AND isocode = '$language'"); + if (Database::num_rows($sqlResult)) { + $result = Database::fetch_array($sqlResult); + return $result['english_name']; + } + return false; } diff --git a/main/auth/external_login/facebook.init.php b/main/auth/external_login/facebook.init.php index cbcefc3163..0840271a6f 100755 --- a/main/auth/external_login/facebook.init.php +++ b/main/auth/external_login/facebook.init.php @@ -14,19 +14,14 @@ /** * Facebook application setting * */ -require_once dirname(__FILE__) . '/facebook-php-sdk/src/facebook.php'; -global $facebook_config; -//Loads the portal facebook settings -$conf = dirname(__FILE__) . '../../inc/conf/auth.conf.php'; + +//Loads the portal facebook settings /** * See facebook section of the auth.conf.php file */ -global $facebook; -$facebook = new Facebook(array( - 'appId' => $facebook_config['appId'], - 'secret' => $facebook_config['secret'] - )); -require_once dirname(__FILE__) . '/facebook.inc.php'; \ No newline at end of file + +require_once dirname(__FILE__) . '/../../inc/conf/auth.conf.php'; + diff --git a/plugin/add_facebook_login_button/img/cnx_fb.png b/plugin/add_facebook_login_button/img/cnx_fb.png index 58bf00cc87..6bd957cef0 100755 Binary files a/plugin/add_facebook_login_button/img/cnx_fb.png and b/plugin/add_facebook_login_button/img/cnx_fb.png differ diff --git a/plugin/add_facebook_login_button/index.php b/plugin/add_facebook_login_button/index.php index e7a2642d5a..04b1790e71 100755 --- a/plugin/add_facebook_login_button/index.php +++ b/plugin/add_facebook_login_button/index.php @@ -1,11 +1,10 @@ addElement( diff --git a/plugin/add_facebook_login_button/template.tpl b/plugin/add_facebook_login_button/template.tpl index beff449380..b77e53e30c 100755 --- a/plugin/add_facebook_login_button/template.tpl +++ b/plugin/add_facebook_login_button/template.tpl @@ -1,7 +1,5 @@ {% if add_facebook_login_button.show_message %} -
- - - -
+ + + {% endif %}