/tests/tests.php

http://github.com/facebook/php-sdk · PHP · 940 lines · 771 code · 106 blank · 63 comment · 3 complexity · 79565ad7bdae96bbe5c7dd11f7002196 MD5 · raw file

  1. <?php
  2. /**
  3. * Copyright 2011 Facebook, Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License"); you may
  6. * not use this file except in compliance with the License. You may obtain
  7. * a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  13. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  14. * License for the specific language governing permissions and limitations
  15. * under the License.
  16. */
  17. class PHPSDKTestCase extends PHPUnit_Framework_TestCase {
  18. const APP_ID = '117743971608120';
  19. const SECRET = '943716006e74d9b9283d4d5d8ab93204';
  20. const MIGRATED_APP_ID = '174236045938435';
  21. const MIGRATED_SECRET = '0073dce2d95c4a5c2922d1827ea0cca6';
  22. private static $kExpiredAccessToken = '206492729383450|2.N4RKywNPuHAey7CK56_wmg__.3600.1304560800.1-214707|6Q14AfpYi_XJB26aRQumouzJiGA';
  23. private static $kValidSignedRequest = '1sxR88U4SW9m6QnSxwCEw_CObqsllXhnpP5j2pxD97c.eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsImV4cGlyZXMiOjEyODEwNTI4MDAsIm9hdXRoX3Rva2VuIjoiMTE3NzQzOTcxNjA4MTIwfDIuVlNUUWpub3hYVVNYd1RzcDB1U2g5d19fLjg2NDAwLjEyODEwNTI4MDAtMTY3Nzg0NjM4NXx4NURORHBtcy1nMUM0dUJHQVYzSVdRX2pYV0kuIiwidXNlcl9pZCI6IjE2Nzc4NDYzODUifQ';
  24. private static $kNonTosedSignedRequest = 'c0Ih6vYvauDwncv0n0pndr0hP0mvZaJPQDPt6Z43O0k.eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiJ9';
  25. private static $kSignedRequestWithBogusSignature = '1sxR32U4SW9m6QnSxwCEw_CObqsllXhnpP5j2pxD97c.eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsImV4cGlyZXMiOjEyODEwNTI4MDAsIm9hdXRoX3Rva2VuIjoiMTE3NzQzOTcxNjA4MTIwfDIuVlNUUWpub3hYVVNYd1RzcDB1U2g5d19fLjg2NDAwLjEyODEwNTI4MDAtMTY3Nzg0NjM4NXx4NURORHBtcy1nMUM0dUJHQVYzSVdRX2pYV0kuIiwidXNlcl9pZCI6IjE2Nzc4NDYzODUifQ';
  26. public function testConstructor() {
  27. $facebook = new TransientFacebook(array(
  28. 'appId' => self::APP_ID,
  29. 'secret' => self::SECRET,
  30. ));
  31. $this->assertEquals($facebook->getAppId(), self::APP_ID,
  32. 'Expect the App ID to be set.');
  33. $this->assertEquals($facebook->getAppSecret(), self::SECRET,
  34. 'Expect the API secret to be set.');
  35. }
  36. public function testConstructorWithFileUpload() {
  37. $facebook = new TransientFacebook(array(
  38. 'appId' => self::APP_ID,
  39. 'secret' => self::SECRET,
  40. 'fileUpload' => true,
  41. ));
  42. $this->assertEquals($facebook->getAppId(), self::APP_ID,
  43. 'Expect the App ID to be set.');
  44. $this->assertEquals($facebook->getAppSecret(), self::SECRET,
  45. 'Expect the API secret to be set.');
  46. $this->assertTrue($facebook->getFileUploadSupport(),
  47. 'Expect file upload support to be on.');
  48. // alias (depricated) for getFileUploadSupport -- test until removed
  49. $this->assertTrue($facebook->useFileUploadSupport(),
  50. 'Expect file upload support to be on.');
  51. }
  52. public function testSetAppId() {
  53. $facebook = new TransientFacebook(array(
  54. 'appId' => self::APP_ID,
  55. 'secret' => self::SECRET,
  56. ));
  57. $facebook->setAppId('dummy');
  58. $this->assertEquals($facebook->getAppId(), 'dummy',
  59. 'Expect the App ID to be dummy.');
  60. }
  61. public function testSetAPISecret() {
  62. $facebook = new TransientFacebook(array(
  63. 'appId' => self::APP_ID,
  64. 'secret' => self::SECRET,
  65. ));
  66. $facebook->setApiSecret('dummy');
  67. $this->assertEquals($facebook->getApiSecret(), 'dummy',
  68. 'Expect the API secret to be dummy.');
  69. }
  70. public function testSetAPPSecret() {
  71. $facebook = new TransientFacebook(array(
  72. 'appId' => self::APP_ID,
  73. 'secret' => self::SECRET,
  74. ));
  75. $facebook->setAppSecret('dummy');
  76. $this->assertEquals($facebook->getAppSecret(), 'dummy',
  77. 'Expect the API secret to be dummy.');
  78. }
  79. public function testSetAccessToken() {
  80. $facebook = new TransientFacebook(array(
  81. 'appId' => self::APP_ID,
  82. 'secret' => self::SECRET,
  83. ));
  84. $facebook->setAccessToken('saltydog');
  85. $this->assertEquals($facebook->getAccessToken(), 'saltydog',
  86. 'Expect installed access token to remain \'saltydog\'');
  87. }
  88. public function testSetFileUploadSupport() {
  89. $facebook = new TransientFacebook(array(
  90. 'appId' => self::APP_ID,
  91. 'secret' => self::SECRET,
  92. ));
  93. $this->assertFalse($facebook->getFileUploadSupport(),
  94. 'Expect file upload support to be off.');
  95. // alias for getFileUploadSupport (depricated), testing until removed
  96. $this->assertFalse($facebook->useFileUploadSupport(),
  97. 'Expect file upload support to be off.');
  98. $facebook->setFileUploadSupport(true);
  99. $this->assertTrue($facebook->getFileUploadSupport(),
  100. 'Expect file upload support to be on.');
  101. // alias for getFileUploadSupport (depricated), testing until removed
  102. $this->assertTrue($facebook->useFileUploadSupport(),
  103. 'Expect file upload support to be on.');
  104. }
  105. public function testGetCurrentURL() {
  106. $facebook = new FBGetCurrentURLFacebook(array(
  107. 'appId' => self::APP_ID,
  108. 'secret' => self::SECRET,
  109. ));
  110. // fake the HPHP $_SERVER globals
  111. $_SERVER['HTTP_HOST'] = 'www.test.com';
  112. $_SERVER['REQUEST_URI'] = '/unit-tests.php?one=one&two=two&three=three';
  113. $current_url = $facebook->publicGetCurrentUrl();
  114. $this->assertEquals(
  115. 'http://www.test.com/unit-tests.php?one=one&two=two&three=three',
  116. $current_url,
  117. 'getCurrentUrl function is changing the current URL');
  118. // ensure structure of valueless GET params is retained (sometimes
  119. // an = sign was present, and sometimes it was not)
  120. // first test when equal signs are present
  121. $_SERVER['HTTP_HOST'] = 'www.test.com';
  122. $_SERVER['REQUEST_URI'] = '/unit-tests.php?one=&two=&three=';
  123. $current_url = $facebook->publicGetCurrentUrl();
  124. $this->assertEquals(
  125. 'http://www.test.com/unit-tests.php?one=&two=&three=',
  126. $current_url,
  127. 'getCurrentUrl function is changing the current URL');
  128. // now confirm that
  129. $_SERVER['HTTP_HOST'] = 'www.test.com';
  130. $_SERVER['REQUEST_URI'] = '/unit-tests.php?one&two&three';
  131. $current_url = $facebook->publicGetCurrentUrl();
  132. $this->assertEquals(
  133. 'http://www.test.com/unit-tests.php?one&two&three',
  134. $current_url,
  135. 'getCurrentUrl function is changing the current URL');
  136. }
  137. public function testGetLoginURL() {
  138. $facebook = new Facebook(array(
  139. 'appId' => self::APP_ID,
  140. 'secret' => self::SECRET,
  141. ));
  142. // fake the HPHP $_SERVER globals
  143. $_SERVER['HTTP_HOST'] = 'www.test.com';
  144. $_SERVER['REQUEST_URI'] = '/unit-tests.php';
  145. $login_url = parse_url($facebook->getLoginUrl());
  146. $this->assertEquals($login_url['scheme'], 'https');
  147. $this->assertEquals($login_url['host'], 'www.facebook.com');
  148. $this->assertEquals($login_url['path'], '/dialog/oauth');
  149. $expected_login_params =
  150. array('client_id' => self::APP_ID,
  151. 'redirect_uri' => 'http://www.test.com/unit-tests.php');
  152. $query_map = array();
  153. parse_str($login_url['query'], $query_map);
  154. $this->assertIsSubset($expected_login_params, $query_map);
  155. // we don't know what the state is, but we know it's an md5 and should
  156. // be 32 characters long.
  157. $this->assertEquals(strlen($query_map['state']), $num_characters = 32);
  158. }
  159. public function testGetLoginURLWithExtraParams() {
  160. $facebook = new Facebook(array(
  161. 'appId' => self::APP_ID,
  162. 'secret' => self::SECRET,
  163. ));
  164. // fake the HPHP $_SERVER globals
  165. $_SERVER['HTTP_HOST'] = 'www.test.com';
  166. $_SERVER['REQUEST_URI'] = '/unit-tests.php';
  167. $extra_params = array('scope' => 'email, sms',
  168. 'nonsense' => 'nonsense');
  169. $login_url = parse_url($facebook->getLoginUrl($extra_params));
  170. $this->assertEquals($login_url['scheme'], 'https');
  171. $this->assertEquals($login_url['host'], 'www.facebook.com');
  172. $this->assertEquals($login_url['path'], '/dialog/oauth');
  173. $expected_login_params =
  174. array_merge(
  175. array('client_id' => self::APP_ID,
  176. 'redirect_uri' => 'http://www.test.com/unit-tests.php'),
  177. $extra_params);
  178. $query_map = array();
  179. parse_str($login_url['query'], $query_map);
  180. $this->assertIsSubset($expected_login_params, $query_map);
  181. // we don't know what the state is, but we know it's an md5 and should
  182. // be 32 characters long.
  183. $this->assertEquals(strlen($query_map['state']), $num_characters = 32);
  184. }
  185. public function testGetLoginURLWithScopeParamsAsArray() {
  186. $facebook = new Facebook(array(
  187. 'appId' => self::APP_ID,
  188. 'secret' => self::SECRET,
  189. ));
  190. // fake the HPHP $_SERVER globals
  191. $_SERVER['HTTP_HOST'] = 'www.test.com';
  192. $_SERVER['REQUEST_URI'] = '/unit-tests.php';
  193. $scope_params_as_array = array('email','sms','read_stream');
  194. $extra_params = array('scope' => $scope_params_as_array,
  195. 'nonsense' => 'nonsense');
  196. $login_url = parse_url($facebook->getLoginUrl($extra_params));
  197. $this->assertEquals($login_url['scheme'], 'https');
  198. $this->assertEquals($login_url['host'], 'www.facebook.com');
  199. $this->assertEquals($login_url['path'], '/dialog/oauth');
  200. // expect api to flatten array params to comma separated list
  201. // should do the same here before asserting to make sure API is behaving
  202. // correctly;
  203. $extra_params['scope'] = implode(',', $scope_params_as_array);
  204. $expected_login_params =
  205. array_merge(
  206. array('client_id' => self::APP_ID,
  207. 'redirect_uri' => 'http://www.test.com/unit-tests.php'),
  208. $extra_params);
  209. $query_map = array();
  210. parse_str($login_url['query'], $query_map);
  211. $this->assertIsSubset($expected_login_params, $query_map);
  212. // we don't know what the state is, but we know it's an md5 and should
  213. // be 32 characters long.
  214. $this->assertEquals(strlen($query_map['state']), $num_characters = 32);
  215. }
  216. public function testGetCodeWithValidCSRFState() {
  217. $facebook = new FBCode(array(
  218. 'appId' => self::APP_ID,
  219. 'secret' => self::SECRET,
  220. ));
  221. $facebook->setCSRFStateToken();
  222. $code = $_REQUEST['code'] = $this->generateMD5HashOfRandomValue();
  223. $_REQUEST['state'] = $facebook->getCSRFStateToken();
  224. $this->assertEquals($code,
  225. $facebook->publicGetCode(),
  226. 'Expect code to be pulled from $_REQUEST[\'code\']');
  227. }
  228. public function testGetCodeWithInvalidCSRFState() {
  229. $facebook = new FBCode(array(
  230. 'appId' => self::APP_ID,
  231. 'secret' => self::SECRET,
  232. ));
  233. $facebook->setCSRFStateToken();
  234. $code = $_REQUEST['code'] = $this->generateMD5HashOfRandomValue();
  235. $_REQUEST['state'] = $facebook->getCSRFStateToken().'forgery!!!';
  236. $this->assertFalse($facebook->publicGetCode(),
  237. 'Expect getCode to fail, CSRF state should not match.');
  238. }
  239. public function testGetCodeWithMissingCSRFState() {
  240. $facebook = new FBCode(array(
  241. 'appId' => self::APP_ID,
  242. 'secret' => self::SECRET,
  243. ));
  244. $code = $_REQUEST['code'] = $this->generateMD5HashOfRandomValue();
  245. // intentionally don't set CSRF token at all
  246. $this->assertFalse($facebook->publicGetCode(),
  247. 'Expect getCode to fail, CSRF state not sent back.');
  248. }
  249. public function testGetUserFromSignedRequest() {
  250. $facebook = new TransientFacebook(array(
  251. 'appId' => self::APP_ID,
  252. 'secret' => self::SECRET,
  253. ));
  254. $_REQUEST['signed_request'] = self::$kValidSignedRequest;
  255. $this->assertEquals('1677846385', $facebook->getUser(),
  256. 'Failed to get user ID from a valid signed request.');
  257. }
  258. public function testGetSignedRequestFromCookie() {
  259. $facebook = new FBGetSignedRequestCookieFacebook(array(
  260. 'appId' => self::APP_ID,
  261. 'secret' => self::SECRET,
  262. ));
  263. $_COOKIE[$facebook->publicGetSignedRequestCookieName()] =
  264. self::$kValidSignedRequest;
  265. $this->assertNotNull($facebook->publicGetSignedRequest());
  266. $this->assertEquals('1677846385', $facebook->getUser(),
  267. 'Failed to get user ID from a valid signed request.');
  268. }
  269. public function testGetSignedRequestWithIncorrectSignature() {
  270. $facebook = new FBGetSignedRequestCookieFacebook(array(
  271. 'appId' => self::APP_ID,
  272. 'secret' => self::SECRET,
  273. ));
  274. $_COOKIE[$facebook->publicGetSignedRequestCookieName()] =
  275. self::$kSignedRequestWithBogusSignature;
  276. $this->assertNull($facebook->publicGetSignedRequest());
  277. }
  278. public function testNonUserAccessToken() {
  279. $facebook = new FBAccessToken(array(
  280. 'appId' => self::APP_ID,
  281. 'secret' => self::SECRET,
  282. ));
  283. // no cookies, and no request params, so no user or code,
  284. // so no user access token (even with cookie support)
  285. $this->assertEquals($facebook->publicGetApplicationAccessToken(),
  286. $facebook->getAccessToken(),
  287. 'Access token should be that for logged out users.');
  288. }
  289. public function testAPIForLoggedOutUsers() {
  290. $facebook = new TransientFacebook(array(
  291. 'appId' => self::APP_ID,
  292. 'secret' => self::SECRET,
  293. ));
  294. $response = $facebook->api(array(
  295. 'method' => 'fql.query',
  296. 'query' => 'SELECT name FROM user WHERE uid=4',
  297. ));
  298. $this->assertEquals(count($response), 1,
  299. 'Expect one row back.');
  300. $this->assertEquals($response[0]['name'], 'Mark Zuckerberg',
  301. 'Expect the name back.');
  302. }
  303. public function testAPIWithBogusAccessToken() {
  304. $facebook = new TransientFacebook(array(
  305. 'appId' => self::APP_ID,
  306. 'secret' => self::SECRET,
  307. ));
  308. $facebook->setAccessToken('this-is-not-really-an-access-token');
  309. // if we don't set an access token and there's no way to
  310. // get one, then the FQL query below works beautifully, handing
  311. // over Zuck's public data. But if you specify a bogus access
  312. // token as I have right here, then the FQL query should fail.
  313. // We could return just Zuck's public data, but that wouldn't
  314. // advertise the issue that the access token is at worst broken
  315. // and at best expired.
  316. try {
  317. $response = $facebook->api(array(
  318. 'method' => 'fql.query',
  319. 'query' => 'SELECT name FROM profile WHERE id=4',
  320. ));
  321. $this->fail('Should not get here.');
  322. } catch(FacebookApiException $e) {
  323. $result = $e->getResult();
  324. $this->assertTrue(is_array($result), 'expect a result object');
  325. $this->assertEquals('190', $result['error_code'], 'expect code');
  326. }
  327. }
  328. public function testAPIGraphPublicData() {
  329. $facebook = new TransientFacebook(array(
  330. 'appId' => self::APP_ID,
  331. 'secret' => self::SECRET,
  332. ));
  333. $response = $facebook->api('/jerry');
  334. $this->assertEquals(
  335. $response['id'], '214707', 'should get expected id.');
  336. }
  337. public function testGraphAPIWithBogusAccessToken() {
  338. $facebook = new TransientFacebook(array(
  339. 'appId' => self::APP_ID,
  340. 'secret' => self::SECRET,
  341. ));
  342. $facebook->setAccessToken('this-is-not-really-an-access-token');
  343. try {
  344. $response = $facebook->api('/me');
  345. $this->fail('Should not get here.');
  346. } catch(FacebookApiException $e) {
  347. // means the server got the access token and didn't like it
  348. $msg = 'OAuthException: Invalid OAuth access token.';
  349. $this->assertEquals($msg, (string) $e,
  350. 'Expect the invalid OAuth token message.');
  351. }
  352. }
  353. public function testGraphAPIWithExpiredAccessToken() {
  354. $facebook = new TransientFacebook(array(
  355. 'appId' => self::APP_ID,
  356. 'secret' => self::SECRET,
  357. ));
  358. $facebook->setAccessToken(self::$kExpiredAccessToken);
  359. try {
  360. $response = $facebook->api('/me');
  361. $this->fail('Should not get here.');
  362. } catch(FacebookApiException $e) {
  363. // means the server got the access token and didn't like it
  364. $error_msg_start = 'OAuthException: Error validating access token:';
  365. $this->assertTrue(strpos((string) $e, $error_msg_start) === 0,
  366. 'Expect the token validation error message.');
  367. }
  368. }
  369. public function testGraphAPIMethod() {
  370. $facebook = new TransientFacebook(array(
  371. 'appId' => self::APP_ID,
  372. 'secret' => self::SECRET,
  373. ));
  374. try {
  375. // naitik being bold about deleting his entire record....
  376. // let's hope this never actually passes.
  377. $response = $facebook->api('/naitik', $method = 'DELETE');
  378. $this->fail('Should not get here.');
  379. } catch(FacebookApiException $e) {
  380. // ProfileDelete means the server understood the DELETE
  381. $msg =
  382. 'OAuthException: (#200) User cannot access this application';
  383. $this->assertEquals($msg, (string) $e,
  384. 'Expect the invalid session message.');
  385. }
  386. }
  387. public function testGraphAPIOAuthSpecError() {
  388. $facebook = new TransientFacebook(array(
  389. 'appId' => self::MIGRATED_APP_ID,
  390. 'secret' => self::MIGRATED_SECRET,
  391. ));
  392. try {
  393. $response = $facebook->api('/me', array(
  394. 'client_id' => self::MIGRATED_APP_ID));
  395. $this->fail('Should not get here.');
  396. } catch(FacebookApiException $e) {
  397. // means the server got the access token
  398. $msg = 'invalid_request: An active access token must be used '.
  399. 'to query information about the current user.';
  400. $this->assertEquals($msg, (string) $e,
  401. 'Expect the invalid session message.');
  402. }
  403. }
  404. public function testGraphAPIMethodOAuthSpecError() {
  405. $facebook = new TransientFacebook(array(
  406. 'appId' => self::MIGRATED_APP_ID,
  407. 'secret' => self::MIGRATED_SECRET,
  408. ));
  409. try {
  410. $response = $facebook->api('/daaku.shah', 'DELETE', array(
  411. 'client_id' => self::MIGRATED_APP_ID));
  412. $this->fail('Should not get here.');
  413. } catch(FacebookApiException $e) {
  414. $this->assertEquals(strpos($e, 'invalid_request'), 0);
  415. }
  416. }
  417. public function testCurlFailure() {
  418. $facebook = new TransientFacebook(array(
  419. 'appId' => self::APP_ID,
  420. 'secret' => self::SECRET,
  421. ));
  422. if (!defined('CURLOPT_TIMEOUT_MS')) {
  423. // can't test it if we don't have millisecond timeouts
  424. return;
  425. }
  426. $exception = null;
  427. try {
  428. // we dont expect facebook will ever return in 1ms
  429. Facebook::$CURL_OPTS[CURLOPT_TIMEOUT_MS] = 50;
  430. $facebook->api('/naitik');
  431. } catch(FacebookApiException $e) {
  432. $exception = $e;
  433. }
  434. unset(Facebook::$CURL_OPTS[CURLOPT_TIMEOUT_MS]);
  435. if (!$exception) {
  436. $this->fail('no exception was thrown on timeout.');
  437. }
  438. $this->assertEquals(
  439. CURLE_OPERATION_TIMEOUTED, $exception->getCode(), 'expect timeout');
  440. $this->assertEquals('CurlException', $exception->getType(), 'expect type');
  441. }
  442. public function testGraphAPIWithOnlyParams() {
  443. $facebook = new TransientFacebook(array(
  444. 'appId' => self::APP_ID,
  445. 'secret' => self::SECRET,
  446. ));
  447. $response = $facebook->api('/jerry');
  448. $this->assertTrue(isset($response['id']),
  449. 'User ID should be public.');
  450. $this->assertTrue(isset($response['name']),
  451. 'User\'s name should be public.');
  452. $this->assertTrue(isset($response['first_name']),
  453. 'User\'s first name should be public.');
  454. $this->assertTrue(isset($response['last_name']),
  455. 'User\'s last name should be public.');
  456. $this->assertFalse(isset($response['work']),
  457. 'User\'s work history should only be available with '.
  458. 'a valid access token.');
  459. $this->assertFalse(isset($response['education']),
  460. 'User\'s education history should only be '.
  461. 'available with a valid access token.');
  462. $this->assertFalse(isset($response['verified']),
  463. 'User\'s verification status should only be '.
  464. 'available with a valid access token.');
  465. }
  466. public function testLoginURLDefaults() {
  467. $_SERVER['HTTP_HOST'] = 'fbrell.com';
  468. $_SERVER['REQUEST_URI'] = '/examples';
  469. $facebook = new TransientFacebook(array(
  470. 'appId' => self::APP_ID,
  471. 'secret' => self::SECRET,
  472. ));
  473. $encodedUrl = rawurlencode('http://fbrell.com/examples');
  474. $this->assertNotNull(strpos($facebook->getLoginUrl(), $encodedUrl),
  475. 'Expect the current url to exist.');
  476. }
  477. public function testLoginURLDefaultsDropStateQueryParam() {
  478. $_SERVER['HTTP_HOST'] = 'fbrell.com';
  479. $_SERVER['REQUEST_URI'] = '/examples?state=xx42xx';
  480. $facebook = new TransientFacebook(array(
  481. 'appId' => self::APP_ID,
  482. 'secret' => self::SECRET,
  483. ));
  484. $expectEncodedUrl = rawurlencode('http://fbrell.com/examples');
  485. $this->assertTrue(strpos($facebook->getLoginUrl(), $expectEncodedUrl) > -1,
  486. 'Expect the current url to exist.');
  487. $this->assertFalse(strpos($facebook->getLoginUrl(), 'xx42xx'),
  488. 'Expect the session param to be dropped.');
  489. }
  490. public function testLoginURLDefaultsDropCodeQueryParam() {
  491. $_SERVER['HTTP_HOST'] = 'fbrell.com';
  492. $_SERVER['REQUEST_URI'] = '/examples?code=xx42xx';
  493. $facebook = new TransientFacebook(array(
  494. 'appId' => self::APP_ID,
  495. 'secret' => self::SECRET,
  496. ));
  497. $expectEncodedUrl = rawurlencode('http://fbrell.com/examples');
  498. $this->assertTrue(strpos($facebook->getLoginUrl(), $expectEncodedUrl) > -1,
  499. 'Expect the current url to exist.');
  500. $this->assertFalse(strpos($facebook->getLoginUrl(), 'xx42xx'),
  501. 'Expect the session param to be dropped.');
  502. }
  503. public function testLoginURLDefaultsDropSignedRequestParamButNotOthers() {
  504. $_SERVER['HTTP_HOST'] = 'fbrell.com';
  505. $_SERVER['REQUEST_URI'] =
  506. '/examples?signed_request=xx42xx&do_not_drop=xx43xx';
  507. $facebook = new TransientFacebook(array(
  508. 'appId' => self::APP_ID,
  509. 'secret' => self::SECRET,
  510. ));
  511. $expectEncodedUrl = rawurlencode('http://fbrell.com/examples');
  512. $this->assertFalse(strpos($facebook->getLoginUrl(), 'xx42xx'),
  513. 'Expect the session param to be dropped.');
  514. $this->assertTrue(strpos($facebook->getLoginUrl(), 'xx43xx') > -1,
  515. 'Expect the do_not_drop param to exist.');
  516. }
  517. public function testLoginURLCustomNext() {
  518. $_SERVER['HTTP_HOST'] = 'fbrell.com';
  519. $_SERVER['REQUEST_URI'] = '/examples';
  520. $facebook = new TransientFacebook(array(
  521. 'appId' => self::APP_ID,
  522. 'secret' => self::SECRET,
  523. ));
  524. $next = 'http://fbrell.com/custom';
  525. $loginUrl = $facebook->getLoginUrl(array(
  526. 'redirect_uri' => $next,
  527. 'cancel_url' => $next
  528. ));
  529. $currentEncodedUrl = rawurlencode('http://fbrell.com/examples');
  530. $expectedEncodedUrl = rawurlencode($next);
  531. $this->assertNotNull(strpos($loginUrl, $expectedEncodedUrl),
  532. 'Expect the custom url to exist.');
  533. $this->assertFalse(strpos($loginUrl, $currentEncodedUrl),
  534. 'Expect the current url to not exist.');
  535. }
  536. public function testLogoutURLDefaults() {
  537. $_SERVER['HTTP_HOST'] = 'fbrell.com';
  538. $_SERVER['REQUEST_URI'] = '/examples';
  539. $facebook = new TransientFacebook(array(
  540. 'appId' => self::APP_ID,
  541. 'secret' => self::SECRET,
  542. ));
  543. $encodedUrl = rawurlencode('http://fbrell.com/examples');
  544. $this->assertNotNull(strpos($facebook->getLogoutUrl(), $encodedUrl),
  545. 'Expect the current url to exist.');
  546. }
  547. public function testLoginStatusURLDefaults() {
  548. $_SERVER['HTTP_HOST'] = 'fbrell.com';
  549. $_SERVER['REQUEST_URI'] = '/examples';
  550. $facebook = new TransientFacebook(array(
  551. 'appId' => self::APP_ID,
  552. 'secret' => self::SECRET,
  553. ));
  554. $encodedUrl = rawurlencode('http://fbrell.com/examples');
  555. $this->assertNotNull(strpos($facebook->getLoginStatusUrl(), $encodedUrl),
  556. 'Expect the current url to exist.');
  557. }
  558. public function testLoginStatusURLCustom() {
  559. $_SERVER['HTTP_HOST'] = 'fbrell.com';
  560. $_SERVER['REQUEST_URI'] = '/examples';
  561. $facebook = new TransientFacebook(array(
  562. 'appId' => self::APP_ID,
  563. 'secret' => self::SECRET,
  564. ));
  565. $encodedUrl1 = rawurlencode('http://fbrell.com/examples');
  566. $okUrl = 'http://fbrell.com/here1';
  567. $encodedUrl2 = rawurlencode($okUrl);
  568. $loginStatusUrl = $facebook->getLoginStatusUrl(array(
  569. 'ok_session' => $okUrl,
  570. ));
  571. $this->assertNotNull(strpos($loginStatusUrl, $encodedUrl1),
  572. 'Expect the current url to exist.');
  573. $this->assertNotNull(strpos($loginStatusUrl, $encodedUrl2),
  574. 'Expect the custom url to exist.');
  575. }
  576. public function testNonDefaultPort() {
  577. $_SERVER['HTTP_HOST'] = 'fbrell.com:8080';
  578. $_SERVER['REQUEST_URI'] = '/examples';
  579. $facebook = new TransientFacebook(array(
  580. 'appId' => self::APP_ID,
  581. 'secret' => self::SECRET,
  582. ));
  583. $encodedUrl = rawurlencode('http://fbrell.com:8080/examples');
  584. $this->assertNotNull(strpos($facebook->getLoginUrl(), $encodedUrl),
  585. 'Expect the current url to exist.');
  586. }
  587. public function testSecureCurrentUrl() {
  588. $_SERVER['HTTP_HOST'] = 'fbrell.com';
  589. $_SERVER['REQUEST_URI'] = '/examples';
  590. $_SERVER['HTTPS'] = 'on';
  591. $facebook = new TransientFacebook(array(
  592. 'appId' => self::APP_ID,
  593. 'secret' => self::SECRET,
  594. ));
  595. $encodedUrl = rawurlencode('https://fbrell.com/examples');
  596. $this->assertNotNull(strpos($facebook->getLoginUrl(), $encodedUrl),
  597. 'Expect the current url to exist.');
  598. }
  599. public function testSecureCurrentUrlWithNonDefaultPort() {
  600. $_SERVER['HTTP_HOST'] = 'fbrell.com:8080';
  601. $_SERVER['REQUEST_URI'] = '/examples';
  602. $_SERVER['HTTPS'] = 'on';
  603. $facebook = new TransientFacebook(array(
  604. 'appId' => self::APP_ID,
  605. 'secret' => self::SECRET,
  606. ));
  607. $encodedUrl = rawurlencode('https://fbrell.com:8080/examples');
  608. $this->assertNotNull(strpos($facebook->getLoginUrl(), $encodedUrl),
  609. 'Expect the current url to exist.');
  610. }
  611. public function testAppSecretCall() {
  612. $facebook = new TransientFacebook(array(
  613. 'appId' => self::APP_ID,
  614. 'secret' => self::SECRET,
  615. ));
  616. try {
  617. $response = $facebook->api('/' . self::APP_ID . '/insights');
  618. $this->fail('Desktop applications need a user token for insights.');
  619. } catch (FacebookApiException $e) {
  620. // this test is failing as the graph call is returning the wrong
  621. // error message
  622. $this->assertTrue(strpos($e->getMessage(),
  623. 'Requires session when calling from a desktop app') !== false,
  624. 'Incorrect exception type thrown when trying to gain ' .
  625. 'insights for desktop app without a user access token.');
  626. } catch (Exception $e) {
  627. $this->fail('Incorrect exception type thrown when trying to gain ' .
  628. 'insights for desktop app without a user access token.');
  629. }
  630. }
  631. public function testBase64UrlEncode() {
  632. $input = 'Facebook rocks';
  633. $output = 'RmFjZWJvb2sgcm9ja3M';
  634. $this->assertEquals(FBPublic::publicBase64UrlDecode($output), $input);
  635. }
  636. public function testSignedToken() {
  637. $facebook = new FBPublic(array(
  638. 'appId' => self::APP_ID,
  639. 'secret' => self::SECRET
  640. ));
  641. $payload = $facebook->publicParseSignedRequest(self::$kValidSignedRequest);
  642. $this->assertNotNull($payload, 'Expected token to parse');
  643. $this->assertEquals($facebook->getSignedRequest(), null);
  644. $_REQUEST['signed_request'] = self::$kValidSignedRequest;
  645. $this->assertEquals($facebook->getSignedRequest(), $payload);
  646. }
  647. public function testNonTossedSignedtoken() {
  648. $facebook = new FBPublic(array(
  649. 'appId' => self::APP_ID,
  650. 'secret' => self::SECRET
  651. ));
  652. $payload = $facebook->publicParseSignedRequest(
  653. self::$kNonTosedSignedRequest);
  654. $this->assertNotNull($payload, 'Expected token to parse');
  655. $this->assertNull($facebook->getSignedRequest());
  656. $_REQUEST['signed_request'] = self::$kNonTosedSignedRequest;
  657. $this->assertEquals($facebook->getSignedRequest(),
  658. array('algorithm' => 'HMAC-SHA256'));
  659. }
  660. public function testBundledCACert() {
  661. $facebook = new TransientFacebook(array(
  662. 'appId' => self::APP_ID,
  663. 'secret' => self::SECRET
  664. ));
  665. // use the bundled cert from the start
  666. Facebook::$CURL_OPTS[CURLOPT_CAINFO] =
  667. dirname(__FILE__) . '/../src/fb_ca_chain_bundle.crt';
  668. $response = $facebook->api('/naitik');
  669. unset(Facebook::$CURL_OPTS[CURLOPT_CAINFO]);
  670. $this->assertEquals(
  671. $response['id'], '5526183', 'should get expected id.');
  672. }
  673. public function testVideoUpload() {
  674. $facebook = new FBRecordURL(array(
  675. 'appId' => self::APP_ID,
  676. 'secret' => self::SECRET
  677. ));
  678. $facebook->api(array('method' => 'video.upload'));
  679. $this->assertContains('//api-video.', $facebook->getRequestedURL(),
  680. 'video.upload should go against api-video');
  681. }
  682. public function testGetUserAndAccessTokenFromSession() {
  683. $facebook = new PersistentFBPublic(array(
  684. 'appId' => self::APP_ID,
  685. 'secret' => self::SECRET
  686. ));
  687. $facebook->publicSetPersistentData('access_token',
  688. self::$kExpiredAccessToken);
  689. $facebook->publicSetPersistentData('user_id', 12345);
  690. $this->assertEquals(self::$kExpiredAccessToken,
  691. $facebook->getAccessToken(),
  692. 'Get access token from persistent store.');
  693. $this->assertEquals('12345',
  694. $facebook->getUser(),
  695. 'Get user id from persistent store.');
  696. }
  697. public function testGetUserAndAccessTokenFromSignedRequestNotSession() {
  698. $facebook = new PersistentFBPublic(array(
  699. 'appId' => self::APP_ID,
  700. 'secret' => self::SECRET
  701. ));
  702. $_REQUEST['signed_request'] = self::$kValidSignedRequest;
  703. $facebook->publicSetPersistentData('user_id', 41572);
  704. $facebook->publicSetPersistentData('access_token',
  705. self::$kExpiredAccessToken);
  706. $this->assertNotEquals('41572', $facebook->getUser(),
  707. 'Got user from session instead of signed request.');
  708. $this->assertEquals('1677846385', $facebook->getUser(),
  709. 'Failed to get correct user ID from signed request.');
  710. $this->assertNotEquals(
  711. self::$kExpiredAccessToken,
  712. $facebook->getAccessToken(),
  713. 'Got access token from session instead of signed request.');
  714. $this->assertNotEmpty(
  715. $facebook->getAccessToken(),
  716. 'Failed to extract an access token from the signed request.');
  717. }
  718. public function testGetUserWithoutCodeOrSignedRequestOrSession() {
  719. $facebook = new PersistentFBPublic(array(
  720. 'appId' => self::APP_ID,
  721. 'secret' => self::SECRET
  722. ));
  723. // deliberately leave $_REQUEST and _$SESSION empty
  724. $this->assertEmpty($_REQUEST,
  725. 'GET, POST, and COOKIE params exist even though '.
  726. 'they should. Test cannot succeed unless all of '.
  727. '$_REQUEST is empty.');
  728. $this->assertEmpty($_SESSION,
  729. 'Session is carrying state and should not be.');
  730. $this->assertEmpty($facebook->getUser(),
  731. 'Got a user id, even without a signed request, '.
  732. 'access token, or session variable.');
  733. $this->assertEmpty($_SESSION,
  734. 'Session superglobal incorrectly populated by getUser.');
  735. }
  736. protected function generateMD5HashOfRandomValue() {
  737. return md5(uniqid(mt_rand(), true));
  738. }
  739. protected function setUp() {
  740. parent::setUp();
  741. }
  742. protected function tearDown() {
  743. $this->clearSuperGlobals();
  744. parent::tearDown();
  745. }
  746. protected function clearSuperGlobals() {
  747. unset($_SERVER['HTTPS']);
  748. unset($_SERVER['HTTP_HOST']);
  749. unset($_SERVER['REQUEST_URI']);
  750. $_SESSION = array();
  751. $_COOKIE = array();
  752. $_REQUEST = array();
  753. $_POST = array();
  754. $_GET = array();
  755. if (session_id()) {
  756. session_destroy();
  757. }
  758. }
  759. /**
  760. * Checks that the correct args are a subset of the returned obj
  761. * @param array $correct The correct array values
  762. * @param array $actual The values in practice
  763. * @param string $message to be shown on failure
  764. */
  765. protected function assertIsSubset($correct, $actual, $msg='') {
  766. foreach ($correct as $key => $value) {
  767. $actual_value = $actual[$key];
  768. $newMsg = (strlen($msg) ? ($msg.' ') : '').'Key: '.$key;
  769. $this->assertEquals($value, $actual_value, $newMsg);
  770. }
  771. }
  772. }
  773. class TransientFacebook extends BaseFacebook {
  774. protected function setPersistentData($key, $value) {}
  775. protected function getPersistentData($key, $default = false) {
  776. return $default;
  777. }
  778. protected function clearPersistentData($key) {}
  779. protected function clearAllPersistentData() {}
  780. }
  781. class FBRecordURL extends TransientFacebook {
  782. private $url;
  783. protected function _oauthRequest($url, $params) {
  784. $this->url = $url;
  785. }
  786. public function getRequestedURL() {
  787. return $this->url;
  788. }
  789. }
  790. class FBPublic extends TransientFacebook {
  791. public static function publicBase64UrlDecode($input) {
  792. return self::base64UrlDecode($input);
  793. }
  794. public function publicParseSignedRequest($input) {
  795. return $this->parseSignedRequest($input);
  796. }
  797. }
  798. class PersistentFBPublic extends Facebook {
  799. public function publicParseSignedRequest($input) {
  800. return $this->parseSignedRequest($input);
  801. }
  802. public function publicSetPersistentData($key, $value) {
  803. $this->setPersistentData($key, $value);
  804. }
  805. }
  806. class FBCode extends Facebook {
  807. public function publicGetCode() {
  808. return $this->getCode();
  809. }
  810. public function setCSRFStateToken() {
  811. $this->establishCSRFTokenState();
  812. }
  813. public function getCSRFStateToken() {
  814. return $this->getPersistentData('state');
  815. }
  816. }
  817. class FBAccessToken extends TransientFacebook {
  818. public function publicGetApplicationAccessToken() {
  819. return $this->getApplicationAccessToken();
  820. }
  821. }
  822. class FBGetCurrentURLFacebook extends TransientFacebook {
  823. public function publicGetCurrentUrl() {
  824. return $this->getCurrentUrl();
  825. }
  826. }
  827. class FBGetSignedRequestCookieFacebook extends TransientFacebook {
  828. public function publicGetSignedRequest() {
  829. return $this->getSignedRequest();
  830. }
  831. public function publicGetSignedRequestCookieName() {
  832. return $this->getSignedRequestCookieName();
  833. }
  834. }