1<?php 2/** 3 * Copyright 2017 Facebook, Inc. 4 * 5 * You are hereby granted a non-exclusive, worldwide, royalty-free license to 6 * use, copy, modify, and distribute this software in source code or binary 7 * form for use in connection with the web services and APIs provided by 8 * Facebook. 9 * 10 * As with any software that integrates with the Facebook platform, your use 11 * of this software is subject to the Facebook Developer Principles and 12 * Policies [http://developers.facebook.com/policy/]. This copyright notice 13 * shall be included in all copies or substantial portions of the software. 14 * 15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 * DEALINGS IN THE SOFTWARE. 22 * 23 */ 24namespace Facebook\Authentication; 25 26use Facebook\Facebook; 27use Facebook\FacebookApp; 28use Facebook\FacebookRequest; 29use Facebook\FacebookResponse; 30use Facebook\FacebookClient; 31use Facebook\Exceptions\FacebookResponseException; 32use Facebook\Exceptions\FacebookSDKException; 33 34/** 35 * Class OAuth2Client 36 * 37 * @package Facebook 38 */ 39class OAuth2Client 40{ 41 /** 42 * @const string The base authorization URL. 43 */ 44 const BASE_AUTHORIZATION_URL = 'https://www.facebook.com'; 45 46 /** 47 * The FacebookApp entity. 48 * 49 * @var FacebookApp 50 */ 51 protected $app; 52 53 /** 54 * The Facebook client. 55 * 56 * @var FacebookClient 57 */ 58 protected $client; 59 60 /** 61 * The version of the Graph API to use. 62 * 63 * @var string 64 */ 65 protected $graphVersion; 66 67 /** 68 * The last request sent to Graph. 69 * 70 * @var FacebookRequest|null 71 */ 72 protected $lastRequest; 73 74 /** 75 * @param FacebookApp $app 76 * @param FacebookClient $client 77 * @param string|null $graphVersion The version of the Graph API to use. 78 */ 79 public function __construct(FacebookApp $app, FacebookClient $client, $graphVersion = null) 80 { 81 $this->app = $app; 82 $this->client = $client; 83 $this->graphVersion = $graphVersion ?: Facebook::DEFAULT_GRAPH_VERSION; 84 } 85 86 /** 87 * Returns the last FacebookRequest that was sent. 88 * Useful for debugging and testing. 89 * 90 * @return FacebookRequest|null 91 */ 92 public function getLastRequest() 93 { 94 return $this->lastRequest; 95 } 96 97 /** 98 * Get the metadata associated with the access token. 99 * 100 * @param AccessToken|string $accessToken The access token to debug. 101 * 102 * @return AccessTokenMetadata 103 */ 104 public function debugToken($accessToken) 105 { 106 $accessToken = $accessToken instanceof AccessToken ? $accessToken->getValue() : $accessToken; 107 $params = ['input_token' => $accessToken]; 108 109 $this->lastRequest = new FacebookRequest( 110 $this->app, 111 $this->app->getAccessToken(), 112 'GET', 113 '/debug_token', 114 $params, 115 null, 116 $this->graphVersion 117 ); 118 $response = $this->client->sendRequest($this->lastRequest); 119 $metadata = $response->getDecodedBody(); 120 121 return new AccessTokenMetadata($metadata); 122 } 123 124 /** 125 * Generates an authorization URL to begin the process of authenticating a user. 126 * 127 * @param string $redirectUrl The callback URL to redirect to. 128 * @param string $state The CSPRNG-generated CSRF value. 129 * @param array $scope An array of permissions to request. 130 * @param array $params An array of parameters to generate URL. 131 * @param string $separator The separator to use in http_build_query(). 132 * 133 * @return string 134 */ 135 public function getAuthorizationUrl($redirectUrl, $state, array $scope = [], array $params = [], $separator = '&') 136 { 137 $params += [ 138 'client_id' => $this->app->getId(), 139 'state' => $state, 140 'response_type' => 'code', 141 'sdk' => 'php-sdk-' . Facebook::VERSION, 142 'redirect_uri' => $redirectUrl, 143 'scope' => implode(',', $scope) 144 ]; 145 146 return static::BASE_AUTHORIZATION_URL . '/' . $this->graphVersion . '/dialog/oauth?' . http_build_query($params, null, $separator); 147 } 148 149 /** 150 * Get a valid access token from a code. 151 * 152 * @param string $code 153 * @param string $redirectUri 154 * 155 * @return AccessToken 156 * 157 * @throws FacebookSDKException 158 */ 159 public function getAccessTokenFromCode($code, $redirectUri = '') 160 { 161 $params = [ 162 'code' => $code, 163 'redirect_uri' => $redirectUri, 164 ]; 165 166 return $this->requestAnAccessToken($params); 167 } 168 169 /** 170 * Exchanges a short-lived access token with a long-lived access token. 171 * 172 * @param AccessToken|string $accessToken 173 * 174 * @return AccessToken 175 * 176 * @throws FacebookSDKException 177 */ 178 public function getLongLivedAccessToken($accessToken) 179 { 180 $accessToken = $accessToken instanceof AccessToken ? $accessToken->getValue() : $accessToken; 181 $params = [ 182 'grant_type' => 'fb_exchange_token', 183 'fb_exchange_token' => $accessToken, 184 ]; 185 186 return $this->requestAnAccessToken($params); 187 } 188 189 /** 190 * Get a valid code from an access token. 191 * 192 * @param AccessToken|string $accessToken 193 * @param string $redirectUri 194 * 195 * @return AccessToken 196 * 197 * @throws FacebookSDKException 198 */ 199 public function getCodeFromLongLivedAccessToken($accessToken, $redirectUri = '') 200 { 201 $params = [ 202 'redirect_uri' => $redirectUri, 203 ]; 204 205 $response = $this->sendRequestWithClientParams('/oauth/client_code', $params, $accessToken); 206 $data = $response->getDecodedBody(); 207 208 if (!isset($data['code'])) { 209 throw new FacebookSDKException('Code was not returned from Graph.', 401); 210 } 211 212 return $data['code']; 213 } 214 215 /** 216 * Send a request to the OAuth endpoint. 217 * 218 * @param array $params 219 * 220 * @return AccessToken 221 * 222 * @throws FacebookSDKException 223 */ 224 protected function requestAnAccessToken(array $params) 225 { 226 $response = $this->sendRequestWithClientParams('/oauth/access_token', $params); 227 $data = $response->getDecodedBody(); 228 229 if (!isset($data['access_token'])) { 230 throw new FacebookSDKException('Access token was not returned from Graph.', 401); 231 } 232 233 // Graph returns two different key names for expiration time 234 // on the same endpoint. Doh! :/ 235 $expiresAt = 0; 236 if (isset($data['expires'])) { 237 // For exchanging a short lived token with a long lived token. 238 // The expiration time in seconds will be returned as "expires". 239 $expiresAt = time() + $data['expires']; 240 } elseif (isset($data['expires_in'])) { 241 // For exchanging a code for a short lived access token. 242 // The expiration time in seconds will be returned as "expires_in". 243 // See: https://developers.facebook.com/docs/facebook-login/access-tokens#long-via-code 244 $expiresAt = time() + $data['expires_in']; 245 } 246 247 return new AccessToken($data['access_token'], $expiresAt); 248 } 249 250 /** 251 * Send a request to Graph with an app access token. 252 * 253 * @param string $endpoint 254 * @param array $params 255 * @param AccessToken|string|null $accessToken 256 * 257 * @return FacebookResponse 258 * 259 * @throws FacebookResponseException 260 */ 261 protected function sendRequestWithClientParams($endpoint, array $params, $accessToken = null) 262 { 263 $params += $this->getClientParams(); 264 265 $accessToken = $accessToken ?: $this->app->getAccessToken(); 266 267 $this->lastRequest = new FacebookRequest( 268 $this->app, 269 $accessToken, 270 'GET', 271 $endpoint, 272 $params, 273 null, 274 $this->graphVersion 275 ); 276 277 return $this->client->sendRequest($this->lastRequest); 278 } 279 280 /** 281 * Returns the client_* params for OAuth requests. 282 * 283 * @return array 284 */ 285 protected function getClientParams() 286 { 287 return [ 288 'client_id' => $this->app->getId(), 289 'client_secret' => $this->app->getSecret(), 290 ]; 291 } 292} 293