No Description

OAuth2Test.php 28KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874
  1. <?php
  2. /*
  3. * Copyright 2010 Google Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain 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,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. namespace Google\Auth\Tests;
  18. use Google\Auth\OAuth2;
  19. use GuzzleHttp\Psr7;
  20. use GuzzleHttp\Psr7\Response;
  21. use PHPUnit\Framework\TestCase;
  22. class OAuth2AuthorizationUriTest extends TestCase
  23. {
  24. private $minimal = [
  25. 'authorizationUri' => 'https://accounts.test.org/insecure/url',
  26. 'redirectUri' => 'https://accounts.test.org/redirect/url',
  27. 'clientId' => 'aClientID',
  28. ];
  29. /**
  30. * @expectedException InvalidArgumentException
  31. */
  32. public function testIsNullIfAuthorizationUriIsNull()
  33. {
  34. $o = new OAuth2([]);
  35. $this->assertNull($o->buildFullAuthorizationUri());
  36. }
  37. /**
  38. * @expectedException InvalidArgumentException
  39. */
  40. public function testRequiresTheClientId()
  41. {
  42. $o = new OAuth2([
  43. 'authorizationUri' => 'https://accounts.test.org/auth/url',
  44. 'redirectUri' => 'https://accounts.test.org/redirect/url',
  45. ]);
  46. $o->buildFullAuthorizationUri();
  47. }
  48. /**
  49. * @expectedException InvalidArgumentException
  50. */
  51. public function testRequiresTheRedirectUri()
  52. {
  53. $o = new OAuth2([
  54. 'authorizationUri' => 'https://accounts.test.org/auth/url',
  55. 'clientId' => 'aClientID',
  56. ]);
  57. $o->buildFullAuthorizationUri();
  58. }
  59. /**
  60. * @expectedException InvalidArgumentException
  61. */
  62. public function testCannotHavePromptAndApprovalPrompt()
  63. {
  64. $o = new OAuth2([
  65. 'authorizationUri' => 'https://accounts.test.org/auth/url',
  66. 'clientId' => 'aClientID',
  67. ]);
  68. $o->buildFullAuthorizationUri([
  69. 'approval_prompt' => 'an approval prompt',
  70. 'prompt' => 'a prompt',
  71. ]);
  72. }
  73. /**
  74. * @expectedException InvalidArgumentException
  75. */
  76. public function testCannotHaveInsecureAuthorizationUri()
  77. {
  78. $o = new OAuth2([
  79. 'authorizationUri' => 'http://accounts.test.org/insecure/url',
  80. 'redirectUri' => 'https://accounts.test.org/redirect/url',
  81. 'clientId' => 'aClientID',
  82. ]);
  83. $o->buildFullAuthorizationUri();
  84. }
  85. /**
  86. * @expectedException InvalidArgumentException
  87. */
  88. public function testCannotHaveRelativeRedirectUri()
  89. {
  90. $o = new OAuth2([
  91. 'authorizationUri' => 'http://accounts.test.org/insecure/url',
  92. 'redirectUri' => '/redirect/url',
  93. 'clientId' => 'aClientID',
  94. ]);
  95. $o->buildFullAuthorizationUri();
  96. }
  97. public function testHasDefaultXXXTypeParams()
  98. {
  99. $o = new OAuth2($this->minimal);
  100. $q = Psr7\parse_query($o->buildFullAuthorizationUri()->getQuery());
  101. $this->assertEquals('code', $q['response_type']);
  102. $this->assertEquals('offline', $q['access_type']);
  103. }
  104. public function testCanBeUrlObject()
  105. {
  106. $config = array_merge($this->minimal, [
  107. 'authorizationUri' => Psr7\uri_for('https://another/uri'),
  108. ]);
  109. $o = new OAuth2($config);
  110. $this->assertEquals('/uri', $o->buildFullAuthorizationUri()->getPath());
  111. }
  112. public function testCanOverrideParams()
  113. {
  114. $overrides = [
  115. 'access_type' => 'o_access_type',
  116. 'client_id' => 'o_client_id',
  117. 'redirect_uri' => 'o_redirect_uri',
  118. 'response_type' => 'o_response_type',
  119. 'state' => 'o_state',
  120. ];
  121. $config = array_merge($this->minimal, ['state' => 'the_state']);
  122. $o = new OAuth2($config);
  123. $q = Psr7\parse_query($o->buildFullAuthorizationUri($overrides)->getQuery());
  124. $this->assertEquals('o_access_type', $q['access_type']);
  125. $this->assertEquals('o_client_id', $q['client_id']);
  126. $this->assertEquals('o_redirect_uri', $q['redirect_uri']);
  127. $this->assertEquals('o_response_type', $q['response_type']);
  128. $this->assertEquals('o_state', $q['state']);
  129. }
  130. public function testIncludesTheScope()
  131. {
  132. $with_strings = array_merge($this->minimal, ['scope' => 'scope1 scope2']);
  133. $o = new OAuth2($with_strings);
  134. $q = Psr7\parse_query($o->buildFullAuthorizationUri()->getQuery());
  135. $this->assertEquals('scope1 scope2', $q['scope']);
  136. $with_array = array_merge($this->minimal, [
  137. 'scope' => ['scope1', 'scope2'],
  138. ]);
  139. $o = new OAuth2($with_array);
  140. $q = Psr7\parse_query($o->buildFullAuthorizationUri()->getQuery());
  141. $this->assertEquals('scope1 scope2', $q['scope']);
  142. }
  143. public function testRedirectUriPostmessageIsAllowed()
  144. {
  145. $o = new OAuth2([
  146. 'authorizationUri' => 'https://accounts.test.org/insecure/url',
  147. 'redirectUri' => 'postmessage',
  148. 'clientId' => 'aClientID',
  149. ]);
  150. $this->assertEquals('postmessage', $o->getRedirectUri());
  151. $url = $o->buildFullAuthorizationUri();
  152. $parts = parse_url((string)$url);
  153. parse_str($parts['query'], $query);
  154. $this->assertArrayHasKey('redirect_uri', $query);
  155. $this->assertEquals('postmessage', $query['redirect_uri']);
  156. }
  157. }
  158. class OAuth2GrantTypeTest extends TestCase
  159. {
  160. private $minimal = [
  161. 'authorizationUri' => 'https://accounts.test.org/insecure/url',
  162. 'redirectUri' => 'https://accounts.test.org/redirect/url',
  163. 'clientId' => 'aClientID',
  164. ];
  165. public function testReturnsNullIfCannotBeInferred()
  166. {
  167. $o = new OAuth2($this->minimal);
  168. $this->assertNull($o->getGrantType());
  169. }
  170. public function testInfersAuthorizationCode()
  171. {
  172. $o = new OAuth2($this->minimal);
  173. $o->setCode('an auth code');
  174. $this->assertEquals('authorization_code', $o->getGrantType());
  175. }
  176. public function testInfersRefreshToken()
  177. {
  178. $o = new OAuth2($this->minimal);
  179. $o->setRefreshToken('a refresh token');
  180. $this->assertEquals('refresh_token', $o->getGrantType());
  181. }
  182. public function testInfersPassword()
  183. {
  184. $o = new OAuth2($this->minimal);
  185. $o->setPassword('a password');
  186. $o->setUsername('a username');
  187. $this->assertEquals('password', $o->getGrantType());
  188. }
  189. public function testInfersJwtBearer()
  190. {
  191. $o = new OAuth2($this->minimal);
  192. $o->setIssuer('an issuer');
  193. $o->setSigningKey('a key');
  194. $this->assertEquals('urn:ietf:params:oauth:grant-type:jwt-bearer',
  195. $o->getGrantType());
  196. }
  197. public function testSetsKnownTypes()
  198. {
  199. $o = new OAuth2($this->minimal);
  200. foreach (OAuth2::$knownGrantTypes as $t) {
  201. $o->setGrantType($t);
  202. $this->assertEquals($t, $o->getGrantType());
  203. }
  204. }
  205. public function testSetsUrlAsGrantType()
  206. {
  207. $o = new OAuth2($this->minimal);
  208. $o->setGrantType('http://a/grant/url');
  209. $this->assertEquals('http://a/grant/url', $o->getGrantType());
  210. }
  211. }
  212. class OAuth2GetCacheKeyTest extends TestCase
  213. {
  214. private $minimal = [
  215. 'clientID' => 'aClientID',
  216. ];
  217. public function testIsNullWithNoScopes()
  218. {
  219. $o = new OAuth2($this->minimal);
  220. $this->assertNull($o->getCacheKey());
  221. }
  222. public function testIsScopeIfSingleScope()
  223. {
  224. $o = new OAuth2($this->minimal);
  225. $o->setScope('test/scope/1');
  226. $this->assertEquals('test/scope/1', $o->getCacheKey());
  227. }
  228. public function testIsAllScopesWhenScopeIsArray()
  229. {
  230. $o = new OAuth2($this->minimal);
  231. $o->setScope(['test/scope/1', 'test/scope/2']);
  232. $this->assertEquals('test/scope/1:test/scope/2', $o->getCacheKey());
  233. }
  234. }
  235. class OAuth2TimingTest extends TestCase
  236. {
  237. private $minimal = [
  238. 'authorizationUri' => 'https://accounts.test.org/insecure/url',
  239. 'redirectUri' => 'https://accounts.test.org/redirect/url',
  240. 'clientId' => 'aClientID',
  241. ];
  242. public function testIssuedAtDefaultsToNull()
  243. {
  244. $o = new OAuth2($this->minimal);
  245. $this->assertNull($o->getIssuedAt());
  246. }
  247. public function testExpiresAtDefaultsToNull()
  248. {
  249. $o = new OAuth2($this->minimal);
  250. $this->assertNull($o->getExpiresAt());
  251. }
  252. public function testExpiresInDefaultsToNull()
  253. {
  254. $o = new OAuth2($this->minimal);
  255. $this->assertNull($o->getExpiresIn());
  256. }
  257. public function testSettingExpiresInSetsIssuedAt()
  258. {
  259. $o = new OAuth2($this->minimal);
  260. $this->assertNull($o->getIssuedAt());
  261. $aShortWhile = 5;
  262. $o->setExpiresIn($aShortWhile);
  263. $this->assertEquals($aShortWhile, $o->getExpiresIn());
  264. $this->assertNotNull($o->getIssuedAt());
  265. }
  266. public function testSettingExpiresInSetsExpireAt()
  267. {
  268. $o = new OAuth2($this->minimal);
  269. $this->assertNull($o->getExpiresAt());
  270. $aShortWhile = 5;
  271. $o->setExpiresIn($aShortWhile);
  272. $this->assertNotNull($o->getExpiresAt());
  273. $this->assertEquals($aShortWhile, $o->getExpiresAt() - $o->getIssuedAt());
  274. }
  275. public function testIsNotExpiredByDefault()
  276. {
  277. $o = new OAuth2($this->minimal);
  278. $this->assertFalse($o->isExpired());
  279. }
  280. public function testIsNotExpiredIfExpiresAtIsOld()
  281. {
  282. $o = new OAuth2($this->minimal);
  283. $o->setExpiresAt(time() - 2);
  284. $this->assertTrue($o->isExpired());
  285. }
  286. }
  287. class OAuth2GeneralTest extends TestCase
  288. {
  289. private $minimal = [
  290. 'authorizationUri' => 'https://accounts.test.org/insecure/url',
  291. 'redirectUri' => 'https://accounts.test.org/redirect/url',
  292. 'clientId' => 'aClientID',
  293. ];
  294. /**
  295. * @expectedException InvalidArgumentException
  296. */
  297. public function testFailsOnUnknownSigningAlgorithm()
  298. {
  299. $o = new OAuth2($this->minimal);
  300. $o->setSigningAlgorithm('this is definitely not an algorithm name');
  301. }
  302. public function testAllowsKnownSigningAlgorithms()
  303. {
  304. $o = new OAuth2($this->minimal);
  305. foreach (OAuth2::$knownSigningAlgorithms as $a) {
  306. $o->setSigningAlgorithm($a);
  307. $this->assertEquals($a, $o->getSigningAlgorithm());
  308. }
  309. }
  310. /**
  311. * @expectedException InvalidArgumentException
  312. */
  313. public function testFailsOnRelativeRedirectUri()
  314. {
  315. $o = new OAuth2($this->minimal);
  316. $o->setRedirectUri('/relative/url');
  317. }
  318. public function testAllowsUrnRedirectUri()
  319. {
  320. $urn = 'urn:ietf:wg:oauth:2.0:oob';
  321. $o = new OAuth2($this->minimal);
  322. $o->setRedirectUri($urn);
  323. $this->assertEquals($urn, $o->getRedirectUri());
  324. }
  325. }
  326. class OAuth2JwtTest extends TestCase
  327. {
  328. private $signingMinimal = [
  329. 'signingKey' => 'example_key',
  330. 'signingAlgorithm' => 'HS256',
  331. 'scope' => 'https://www.googleapis.com/auth/userinfo.profile',
  332. 'issuer' => 'app@example.com',
  333. 'audience' => 'accounts.google.com',
  334. 'clientId' => 'aClientID',
  335. ];
  336. /**
  337. * @expectedException DomainException
  338. */
  339. public function testFailsWithMissingAudience()
  340. {
  341. $testConfig = $this->signingMinimal;
  342. unset($testConfig['audience']);
  343. $o = new OAuth2($testConfig);
  344. $o->toJwt();
  345. }
  346. /**
  347. * @expectedException DomainException
  348. */
  349. public function testFailsWithMissingIssuer()
  350. {
  351. $testConfig = $this->signingMinimal;
  352. unset($testConfig['issuer']);
  353. $o = new OAuth2($testConfig);
  354. $o->toJwt();
  355. }
  356. /**
  357. */
  358. public function testCanHaveNoScope()
  359. {
  360. $testConfig = $this->signingMinimal;
  361. unset($testConfig['scope']);
  362. $o = new OAuth2($testConfig);
  363. $o->toJwt();
  364. }
  365. /**
  366. * @expectedException DomainException
  367. */
  368. public function testFailsWithMissingSigningKey()
  369. {
  370. $testConfig = $this->signingMinimal;
  371. unset($testConfig['signingKey']);
  372. $o = new OAuth2($testConfig);
  373. $o->toJwt();
  374. }
  375. /**
  376. * @expectedException DomainException
  377. */
  378. public function testFailsWithMissingSigningAlgorithm()
  379. {
  380. $testConfig = $this->signingMinimal;
  381. unset($testConfig['signingAlgorithm']);
  382. $o = new OAuth2($testConfig);
  383. $o->toJwt();
  384. }
  385. public function testCanHS256EncodeAValidPayload()
  386. {
  387. $testConfig = $this->signingMinimal;
  388. $o = new OAuth2($testConfig);
  389. $payload = $o->toJwt();
  390. $roundTrip = $this->jwtDecode($payload, $testConfig['signingKey'], array('HS256'));
  391. $this->assertEquals($roundTrip->iss, $testConfig['issuer']);
  392. $this->assertEquals($roundTrip->aud, $testConfig['audience']);
  393. $this->assertEquals($roundTrip->scope, $testConfig['scope']);
  394. }
  395. public function testCanRS256EncodeAValidPayload()
  396. {
  397. $publicKey = file_get_contents(__DIR__ . '/fixtures' . '/public.pem');
  398. $privateKey = file_get_contents(__DIR__ . '/fixtures' . '/private.pem');
  399. $testConfig = $this->signingMinimal;
  400. $o = new OAuth2($testConfig);
  401. $o->setSigningAlgorithm('RS256');
  402. $o->setSigningKey($privateKey);
  403. $payload = $o->toJwt();
  404. $roundTrip = $this->jwtDecode($payload, $publicKey, array('RS256'));
  405. $this->assertEquals($roundTrip->iss, $testConfig['issuer']);
  406. $this->assertEquals($roundTrip->aud, $testConfig['audience']);
  407. $this->assertEquals($roundTrip->scope, $testConfig['scope']);
  408. }
  409. public function testCanHaveAdditionalClaims()
  410. {
  411. $publicKey = file_get_contents(__DIR__ . '/fixtures' . '/public.pem');
  412. $privateKey = file_get_contents(__DIR__ . '/fixtures' . '/private.pem');
  413. $testConfig = $this->signingMinimal;
  414. $targetAud = '123@456.com';
  415. $testConfig['additionalClaims'] = ['target_audience' => $targetAud];
  416. $o = new OAuth2($testConfig);
  417. $o->setSigningAlgorithm('RS256');
  418. $o->setSigningKey($privateKey);
  419. $payload = $o->toJwt();
  420. $roundTrip = $this->jwtDecode($payload, $publicKey, array('RS256'));
  421. $this->assertEquals($roundTrip->target_audience, $targetAud);
  422. }
  423. private function jwtDecode()
  424. {
  425. $args = func_get_args();
  426. $class = 'JWT';
  427. if (class_exists('Firebase\JWT\JWT')) {
  428. $class = 'Firebase\JWT\JWT';
  429. }
  430. return call_user_func_array("$class::decode", $args);
  431. }
  432. }
  433. class OAuth2GenerateAccessTokenRequestTest extends TestCase
  434. {
  435. private $tokenRequestMinimal = [
  436. 'tokenCredentialUri' => 'https://tokens_r_us/test',
  437. 'scope' => 'https://www.googleapis.com/auth/userinfo.profile',
  438. 'issuer' => 'app@example.com',
  439. 'audience' => 'accounts.google.com',
  440. 'clientId' => 'aClientID',
  441. ];
  442. /**
  443. * @expectedException DomainException
  444. */
  445. public function testFailsIfNoTokenCredentialUri()
  446. {
  447. $testConfig = $this->tokenRequestMinimal;
  448. unset($testConfig['tokenCredentialUri']);
  449. $o = new OAuth2($testConfig);
  450. $o->generateCredentialsRequest();
  451. }
  452. /**
  453. * @expectedException DomainException
  454. */
  455. public function testFailsIfAuthorizationCodeIsMissing()
  456. {
  457. $testConfig = $this->tokenRequestMinimal;
  458. $testConfig['redirectUri'] = 'https://has/redirect/uri';
  459. $o = new OAuth2($testConfig);
  460. $o->generateCredentialsRequest();
  461. }
  462. public function testGeneratesAuthorizationCodeRequests()
  463. {
  464. $testConfig = $this->tokenRequestMinimal;
  465. $testConfig['redirectUri'] = 'https://has/redirect/uri';
  466. $o = new OAuth2($testConfig);
  467. $o->setCode('an_auth_code');
  468. // Generate the request and confirm that it's correct.
  469. $req = $o->generateCredentialsRequest();
  470. $this->assertInstanceOf('Psr\Http\Message\RequestInterface', $req);
  471. $this->assertEquals('POST', $req->getMethod());
  472. $fields = Psr7\parse_query((string)$req->getBody());
  473. $this->assertEquals('authorization_code', $fields['grant_type']);
  474. $this->assertEquals('an_auth_code', $fields['code']);
  475. }
  476. public function testGeneratesPasswordRequests()
  477. {
  478. $testConfig = $this->tokenRequestMinimal;
  479. $o = new OAuth2($testConfig);
  480. $o->setUsername('a_username');
  481. $o->setPassword('a_password');
  482. // Generate the request and confirm that it's correct.
  483. $req = $o->generateCredentialsRequest();
  484. $this->assertInstanceOf('Psr\Http\Message\RequestInterface', $req);
  485. $this->assertEquals('POST', $req->getMethod());
  486. $fields = Psr7\parse_query((string)$req->getBody());
  487. $this->assertEquals('password', $fields['grant_type']);
  488. $this->assertEquals('a_password', $fields['password']);
  489. $this->assertEquals('a_username', $fields['username']);
  490. }
  491. public function testGeneratesRefreshTokenRequests()
  492. {
  493. $testConfig = $this->tokenRequestMinimal;
  494. $o = new OAuth2($testConfig);
  495. $o->setRefreshToken('a_refresh_token');
  496. // Generate the request and confirm that it's correct.
  497. $req = $o->generateCredentialsRequest();
  498. $this->assertInstanceOf('Psr\Http\Message\RequestInterface', $req);
  499. $this->assertEquals('POST', $req->getMethod());
  500. $fields = Psr7\parse_query((string)$req->getBody());
  501. $this->assertEquals('refresh_token', $fields['grant_type']);
  502. $this->assertEquals('a_refresh_token', $fields['refresh_token']);
  503. }
  504. public function testClientSecretAddedIfSetForAuthorizationCodeRequests()
  505. {
  506. $testConfig = $this->tokenRequestMinimal;
  507. $testConfig['clientSecret'] = 'a_client_secret';
  508. $testConfig['redirectUri'] = 'https://has/redirect/uri';
  509. $o = new OAuth2($testConfig);
  510. $o->setCode('an_auth_code');
  511. $request = $o->generateCredentialsRequest();
  512. $fields = Psr7\parse_query((string)$request->getBody());
  513. $this->assertEquals('a_client_secret', $fields['client_secret']);
  514. }
  515. public function testClientSecretAddedIfSetForRefreshTokenRequests()
  516. {
  517. $testConfig = $this->tokenRequestMinimal;
  518. $testConfig['clientSecret'] = 'a_client_secret';
  519. $o = new OAuth2($testConfig);
  520. $o->setRefreshToken('a_refresh_token');
  521. $request = $o->generateCredentialsRequest();
  522. $fields = Psr7\parse_query((string)$request->getBody());
  523. $this->assertEquals('a_client_secret', $fields['client_secret']);
  524. }
  525. public function testClientSecretAddedIfSetForPasswordRequests()
  526. {
  527. $testConfig = $this->tokenRequestMinimal;
  528. $testConfig['clientSecret'] = 'a_client_secret';
  529. $o = new OAuth2($testConfig);
  530. $o->setUsername('a_username');
  531. $o->setPassword('a_password');
  532. $request = $o->generateCredentialsRequest();
  533. $fields = Psr7\parse_query((string)$request->getBody());
  534. $this->assertEquals('a_client_secret', $fields['client_secret']);
  535. }
  536. public function testGeneratesAssertionRequests()
  537. {
  538. $testConfig = $this->tokenRequestMinimal;
  539. $o = new OAuth2($testConfig);
  540. $o->setSigningKey('a_key');
  541. $o->setSigningAlgorithm('HS256');
  542. // Generate the request and confirm that it's correct.
  543. $req = $o->generateCredentialsRequest();
  544. $this->assertInstanceOf('Psr\Http\Message\RequestInterface', $req);
  545. $this->assertEquals('POST', $req->getMethod());
  546. $fields = Psr7\parse_query((string)$req->getBody());
  547. $this->assertEquals(OAuth2::JWT_URN, $fields['grant_type']);
  548. $this->assertArrayHasKey('assertion', $fields);
  549. }
  550. public function testGeneratesExtendedRequests()
  551. {
  552. $testConfig = $this->tokenRequestMinimal;
  553. $o = new OAuth2($testConfig);
  554. $o->setGrantType('urn:my_test_grant_type');
  555. $o->setExtensionParams(['my_param' => 'my_value']);
  556. // Generate the request and confirm that it's correct.
  557. $req = $o->generateCredentialsRequest();
  558. $this->assertInstanceOf('Psr\Http\Message\RequestInterface', $req);
  559. $this->assertEquals('POST', $req->getMethod());
  560. $fields = Psr7\parse_query((string)$req->getBody());
  561. $this->assertEquals('my_value', $fields['my_param']);
  562. $this->assertEquals('urn:my_test_grant_type', $fields['grant_type']);
  563. }
  564. }
  565. class OAuth2FetchAuthTokenTest extends TestCase
  566. {
  567. private $fetchAuthTokenMinimal = [
  568. 'tokenCredentialUri' => 'https://tokens_r_us/test',
  569. 'scope' => 'https://www.googleapis.com/auth/userinfo.profile',
  570. 'signingKey' => 'example_key',
  571. 'signingAlgorithm' => 'HS256',
  572. 'issuer' => 'app@example.com',
  573. 'audience' => 'accounts.google.com',
  574. 'clientId' => 'aClientID',
  575. ];
  576. /**
  577. * @expectedException GuzzleHttp\Exception\ClientException
  578. */
  579. public function testFailsOn400()
  580. {
  581. $testConfig = $this->fetchAuthTokenMinimal;
  582. $httpHandler = getHandler([
  583. buildResponse(400),
  584. ]);
  585. $o = new OAuth2($testConfig);
  586. $o->fetchAuthToken($httpHandler);
  587. }
  588. /**
  589. * @expectedException GuzzleHttp\Exception\ServerException
  590. */
  591. public function testFailsOn500()
  592. {
  593. $testConfig = $this->fetchAuthTokenMinimal;
  594. $httpHandler = getHandler([
  595. buildResponse(500),
  596. ]);
  597. $o = new OAuth2($testConfig);
  598. $o->fetchAuthToken($httpHandler);
  599. }
  600. /**
  601. * @expectedException Exception
  602. * @expectedExceptionMessage Invalid JSON response
  603. */
  604. public function testFailsOnNoContentTypeIfResponseIsNotJSON()
  605. {
  606. $testConfig = $this->fetchAuthTokenMinimal;
  607. $notJson = '{"foo": , this is cannot be passed as json" "bar"}';
  608. $httpHandler = getHandler([
  609. buildResponse(200, [], Psr7\stream_for($notJson)),
  610. ]);
  611. $o = new OAuth2($testConfig);
  612. $o->fetchAuthToken($httpHandler);
  613. }
  614. public function testFetchesJsonResponseOnNoContentTypeOK()
  615. {
  616. $testConfig = $this->fetchAuthTokenMinimal;
  617. $json = '{"foo": "bar"}';
  618. $httpHandler = getHandler([
  619. buildResponse(200, [], Psr7\stream_for($json)),
  620. ]);
  621. $o = new OAuth2($testConfig);
  622. $tokens = $o->fetchAuthToken($httpHandler);
  623. $this->assertEquals($tokens['foo'], 'bar');
  624. }
  625. public function testFetchesFromFormEncodedResponseOK()
  626. {
  627. $testConfig = $this->fetchAuthTokenMinimal;
  628. $json = 'foo=bar&spice=nice';
  629. $httpHandler = getHandler([
  630. buildResponse(
  631. 200,
  632. ['Content-Type' => 'application/x-www-form-urlencoded'],
  633. Psr7\stream_for($json)
  634. ),
  635. ]);
  636. $o = new OAuth2($testConfig);
  637. $tokens = $o->fetchAuthToken($httpHandler);
  638. $this->assertEquals($tokens['foo'], 'bar');
  639. $this->assertEquals($tokens['spice'], 'nice');
  640. }
  641. public function testUpdatesTokenFieldsOnFetch()
  642. {
  643. $testConfig = $this->fetchAuthTokenMinimal;
  644. $wanted_updates = [
  645. 'expires_at' => '1',
  646. 'expires_in' => '57',
  647. 'issued_at' => '2',
  648. 'access_token' => 'an_access_token',
  649. 'id_token' => 'an_id_token',
  650. 'refresh_token' => 'a_refresh_token',
  651. ];
  652. $json = json_encode($wanted_updates);
  653. $httpHandler = getHandler([
  654. buildResponse(200, [], Psr7\stream_for($json)),
  655. ]);
  656. $o = new OAuth2($testConfig);
  657. $this->assertNull($o->getExpiresAt());
  658. $this->assertNull($o->getExpiresIn());
  659. $this->assertNull($o->getIssuedAt());
  660. $this->assertNull($o->getAccessToken());
  661. $this->assertNull($o->getIdToken());
  662. $this->assertNull($o->getRefreshToken());
  663. $tokens = $o->fetchAuthToken($httpHandler);
  664. $this->assertEquals(1, $o->getExpiresAt());
  665. $this->assertEquals(57, $o->getExpiresIn());
  666. $this->assertEquals(2, $o->getIssuedAt());
  667. $this->assertEquals('an_access_token', $o->getAccessToken());
  668. $this->assertEquals('an_id_token', $o->getIdToken());
  669. $this->assertEquals('a_refresh_token', $o->getRefreshToken());
  670. }
  671. public function testUpdatesTokenFieldsOnFetchMissingRefreshToken()
  672. {
  673. $testConfig = $this->fetchAuthTokenMinimal;
  674. $testConfig['refresh_token'] = 'a_refresh_token';
  675. $wanted_updates = [
  676. 'expires_at' => '1',
  677. 'expires_in' => '57',
  678. 'issued_at' => '2',
  679. 'access_token' => 'an_access_token',
  680. 'id_token' => 'an_id_token',
  681. ];
  682. $json = json_encode($wanted_updates);
  683. $httpHandler = getHandler([
  684. buildResponse(200, [], Psr7\stream_for($json)),
  685. ]);
  686. $o = new OAuth2($testConfig);
  687. $this->assertNull($o->getExpiresAt());
  688. $this->assertNull($o->getExpiresIn());
  689. $this->assertNull($o->getIssuedAt());
  690. $this->assertNull($o->getAccessToken());
  691. $this->assertNull($o->getIdToken());
  692. $this->assertEquals('a_refresh_token', $o->getRefreshToken());
  693. $tokens = $o->fetchAuthToken($httpHandler);
  694. $this->assertEquals(1, $o->getExpiresAt());
  695. $this->assertEquals(57, $o->getExpiresIn());
  696. $this->assertEquals(2, $o->getIssuedAt());
  697. $this->assertEquals('an_access_token', $o->getAccessToken());
  698. $this->assertEquals('an_id_token', $o->getIdToken());
  699. $this->assertEquals('a_refresh_token', $o->getRefreshToken());
  700. }
  701. }
  702. class OAuth2VerifyIdTokenTest extends TestCase
  703. {
  704. private $publicKey;
  705. private $privateKey;
  706. private $verifyIdTokenMinimal = [
  707. 'scope' => 'https://www.googleapis.com/auth/userinfo.profile',
  708. 'audience' => 'myaccount.on.host.issuer.com',
  709. 'issuer' => 'an.issuer.com',
  710. 'clientId' => 'myaccount.on.host.issuer.com',
  711. ];
  712. public function setUp()
  713. {
  714. $this->publicKey =
  715. file_get_contents(__DIR__ . '/fixtures' . '/public.pem');
  716. $this->privateKey =
  717. file_get_contents(__DIR__ . '/fixtures' . '/private.pem');
  718. }
  719. /**
  720. * @expectedException UnexpectedValueException
  721. */
  722. public function testFailsIfIdTokenIsInvalid()
  723. {
  724. $testConfig = $this->verifyIdTokenMinimal;
  725. $not_a_jwt = 'not a jot';
  726. $o = new OAuth2($testConfig);
  727. $o->setIdToken($not_a_jwt);
  728. $o->verifyIdToken($this->publicKey);
  729. }
  730. /**
  731. * @expectedException DomainException
  732. */
  733. public function testFailsIfAudienceIsMissing()
  734. {
  735. $testConfig = $this->verifyIdTokenMinimal;
  736. $now = time();
  737. $origIdToken = [
  738. 'issuer' => $testConfig['issuer'],
  739. 'exp' => $now + 65, // arbitrary
  740. 'iat' => $now,
  741. ];
  742. $o = new OAuth2($testConfig);
  743. $jwtIdToken = $this->jwtEncode($origIdToken, $this->privateKey, 'RS256');
  744. $o->setIdToken($jwtIdToken);
  745. $o->verifyIdToken($this->publicKey, ['RS256']);
  746. }
  747. /**
  748. * @expectedException DomainException
  749. */
  750. public function testFailsIfAudienceIsWrong()
  751. {
  752. $now = time();
  753. $testConfig = $this->verifyIdTokenMinimal;
  754. $origIdToken = [
  755. 'aud' => 'a different audience',
  756. 'iss' => $testConfig['issuer'],
  757. 'exp' => $now + 65, // arbitrary
  758. 'iat' => $now,
  759. ];
  760. $o = new OAuth2($testConfig);
  761. $jwtIdToken = $this->jwtEncode($origIdToken, $this->privateKey, 'RS256');
  762. $o->setIdToken($jwtIdToken);
  763. $o->verifyIdToken($this->publicKey, ['RS256']);
  764. }
  765. public function testShouldReturnAValidIdToken()
  766. {
  767. $testConfig = $this->verifyIdTokenMinimal;
  768. $now = time();
  769. $origIdToken = [
  770. 'aud' => $testConfig['audience'],
  771. 'iss' => $testConfig['issuer'],
  772. 'exp' => $now + 65, // arbitrary
  773. 'iat' => $now,
  774. ];
  775. $o = new OAuth2($testConfig);
  776. $alg = 'RS256';
  777. $jwtIdToken = $this->jwtEncode($origIdToken, $this->privateKey, $alg);
  778. $o->setIdToken($jwtIdToken);
  779. $roundTrip = $o->verifyIdToken($this->publicKey, array($alg));
  780. $this->assertEquals($origIdToken['aud'], $roundTrip->aud);
  781. }
  782. private function jwtEncode()
  783. {
  784. $args = func_get_args();
  785. $class = 'JWT';
  786. if (class_exists('Firebase\JWT\JWT')) {
  787. $class = 'Firebase\JWT\JWT';
  788. }
  789. return call_user_func_array("$class::encode", $args);
  790. }
  791. }