AuthHelperTest.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. <?php
  2. /*
  3. * This file is part of Composer.
  4. *
  5. * (c) Nils Adermann <naderman@naderman.de>
  6. * Jordi Boggiano <j.boggiano@seld.be>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace Composer\Test\Util;
  12. use Composer\IO\IOInterface;
  13. use Composer\Test\TestCase;
  14. use Composer\Util\AuthHelper;
  15. use Composer\Util\Bitbucket;
  16. use RuntimeException;
  17. /**
  18. * @author Michael Chekin <mchekin@gmail.com>
  19. */
  20. class AuthHelperTest extends TestCase
  21. {
  22. /** @type \Composer\IO\IOInterface|\PHPUnit_Framework_MockObject_MockObject */
  23. private $io;
  24. /** @type \Composer\Config|\PHPUnit_Framework_MockObject_MockObject */
  25. private $config;
  26. /** @type AuthHelper */
  27. private $authHelper;
  28. protected function setUp()
  29. {
  30. $this->io = $this
  31. ->getMockBuilder('Composer\IO\IOInterface')
  32. ->disableOriginalConstructor()
  33. ->getMock();
  34. $this->config = $this->getMockBuilder('Composer\Config')->getMock();
  35. $this->authHelper = new AuthHelper($this->io, $this->config);
  36. }
  37. public function testAddAuthenticationHeaderWithoutAuthCredentials()
  38. {
  39. $headers = array(
  40. 'Accept-Encoding: gzip',
  41. 'Connection: close'
  42. );
  43. $origin = 'http://example.org';
  44. $url = 'file://' . __FILE__;
  45. $this->io->expects($this->once())
  46. ->method('hasAuthentication')
  47. ->with($origin)
  48. ->willReturn(false);
  49. $this->assertSame(
  50. $headers,
  51. $this->authHelper->addAuthenticationHeader($headers, $origin, $url)
  52. );
  53. }
  54. public function testAddAuthenticationHeaderWithBearerPassword()
  55. {
  56. $headers = array(
  57. 'Accept-Encoding: gzip',
  58. 'Connection: close'
  59. );
  60. $origin = 'http://example.org';
  61. $url = 'file://' . __FILE__;
  62. $auth = array(
  63. 'username' => 'my_username',
  64. 'password' => 'bearer'
  65. );
  66. $this->expectsAuthentication($origin, $auth);
  67. $expectedHeaders = array_merge($headers, array('Authorization: Bearer ' . $auth['username']));
  68. $this->assertSame(
  69. $expectedHeaders,
  70. $this->authHelper->addAuthenticationHeader($headers, $origin, $url)
  71. );
  72. }
  73. public function testAddAuthenticationHeaderWithGithubToken()
  74. {
  75. $headers = array(
  76. 'Accept-Encoding: gzip',
  77. 'Connection: close'
  78. );
  79. $origin = 'github.com';
  80. $url = 'https://api.github.com/';
  81. $auth = array(
  82. 'username' => 'my_username',
  83. 'password' => 'x-oauth-basic'
  84. );
  85. $this->expectsAuthentication($origin, $auth);
  86. $this->io->expects($this->once())
  87. ->method('writeError')
  88. ->with('Using GitHub token authentication', true, IOInterface::DEBUG);
  89. $expectedHeaders = array_merge($headers, array('Authorization: token ' . $auth['username']));
  90. $this->assertSame(
  91. $expectedHeaders,
  92. $this->authHelper->addAuthenticationHeader($headers, $origin, $url)
  93. );
  94. }
  95. public function testAddAuthenticationHeaderWithGitlabOathToken()
  96. {
  97. $headers = array(
  98. 'Accept-Encoding: gzip',
  99. 'Connection: close'
  100. );
  101. $origin = 'gitlab.com';
  102. $url = 'https://api.gitlab.com/';
  103. $auth = array(
  104. 'username' => 'my_username',
  105. 'password' => 'oauth2'
  106. );
  107. $this->expectsAuthentication($origin, $auth);
  108. $this->config->expects($this->once())
  109. ->method('get')
  110. ->with('gitlab-domains')
  111. ->willReturn(array($origin));
  112. $this->io->expects($this->once())
  113. ->method('writeError')
  114. ->with('Using GitLab OAuth token authentication', true, IOInterface::DEBUG);
  115. $expectedHeaders = array_merge($headers, array('Authorization: Bearer ' . $auth['username']));
  116. $this->assertSame(
  117. $expectedHeaders,
  118. $this->authHelper->addAuthenticationHeader($headers, $origin, $url)
  119. );
  120. }
  121. public function gitlabPrivateTokenProvider()
  122. {
  123. return array(
  124. array('private-token'),
  125. array('gitlab-ci-token'),
  126. );
  127. }
  128. /**
  129. * @dataProvider gitlabPrivateTokenProvider
  130. *
  131. * @param string $password
  132. */
  133. public function testAddAuthenticationHeaderWithGitlabPrivateToken($password)
  134. {
  135. $headers = array(
  136. 'Accept-Encoding: gzip',
  137. 'Connection: close'
  138. );
  139. $origin = 'gitlab.com';
  140. $url = 'https://api.gitlab.com/';
  141. $auth = array(
  142. 'username' => 'my_username',
  143. 'password' => $password
  144. );
  145. $this->expectsAuthentication($origin, $auth);
  146. $this->config->expects($this->once())
  147. ->method('get')
  148. ->with('gitlab-domains')
  149. ->willReturn(array($origin));
  150. $this->io->expects($this->once())
  151. ->method('writeError')
  152. ->with('Using GitLab private token authentication', true, IOInterface::DEBUG);
  153. $expectedHeaders = array_merge($headers, array('PRIVATE-TOKEN: ' . $auth['username']));
  154. $this->assertSame(
  155. $expectedHeaders,
  156. $this->authHelper->addAuthenticationHeader($headers, $origin, $url)
  157. );
  158. }
  159. public function testAddAuthenticationHeaderWithBitbucketOathToken()
  160. {
  161. $headers = array(
  162. 'Accept-Encoding: gzip',
  163. 'Connection: close'
  164. );
  165. $origin = 'bitbucket.org';
  166. $url = 'https://bitbucket.org/site/oauth2/authorize';
  167. $auth = array(
  168. 'username' => 'x-token-auth',
  169. 'password' => 'my_password'
  170. );
  171. $this->expectsAuthentication($origin, $auth);
  172. $this->config->expects($this->once())
  173. ->method('get')
  174. ->with('gitlab-domains')
  175. ->willReturn(array());
  176. $this->io->expects($this->once())
  177. ->method('writeError')
  178. ->with('Using Bitbucket OAuth token authentication', true, IOInterface::DEBUG);
  179. $expectedHeaders = array_merge($headers, array('Authorization: Bearer ' . $auth['password']));
  180. $this->assertSame(
  181. $expectedHeaders,
  182. $this->authHelper->addAuthenticationHeader($headers, $origin, $url)
  183. );
  184. }
  185. public function bitbucketPublicUrlProvider()
  186. {
  187. return array(
  188. array('https://bitbucket.org/user/repo/downloads/whatever'),
  189. array('https://bbuseruploads.s3.amazonaws.com/9421ee72-638e-43a9-82ea-39cfaae2bfaa/downloads/b87c59d9-54f3-4922-b711-d89059ec3bcf'),
  190. );
  191. }
  192. /**
  193. * @dataProvider bitbucketPublicUrlProvider
  194. *
  195. * @param string $url
  196. */
  197. public function testAddAuthenticationHeaderWithBitbucketPublicUrl($url)
  198. {
  199. $headers = array(
  200. 'Accept-Encoding: gzip',
  201. 'Connection: close'
  202. );
  203. $origin = 'bitbucket.org';
  204. $auth = array(
  205. 'username' => 'x-token-auth',
  206. 'password' => 'my_password'
  207. );
  208. $this->expectsAuthentication($origin, $auth);
  209. $this->config->expects($this->once())
  210. ->method('get')
  211. ->with('gitlab-domains')
  212. ->willReturn(array());
  213. $this->assertSame(
  214. $headers,
  215. $this->authHelper->addAuthenticationHeader($headers, $origin, $url)
  216. );
  217. }
  218. public function basicHttpAuthenticationProvider()
  219. {
  220. return array(
  221. array(
  222. Bitbucket::OAUTH2_ACCESS_TOKEN_URL,
  223. 'bitbucket.org',
  224. array(
  225. 'username' => 'x-token-auth',
  226. 'password' => 'my_password'
  227. )
  228. ),
  229. array(
  230. 'https://some-api.url.com',
  231. 'some-api.url.com',
  232. array(
  233. 'username' => 'my_username',
  234. 'password' => 'my_password'
  235. )
  236. ),
  237. );
  238. }
  239. /**
  240. * @dataProvider basicHttpAuthenticationProvider
  241. *
  242. * @param string $url
  243. * @param string $origin
  244. * @param array $auth
  245. */
  246. public function testAddAuthenticationHeaderWithBasicHttpAuthentication($url, $origin, $auth)
  247. {
  248. $headers = array(
  249. 'Accept-Encoding: gzip',
  250. 'Connection: close'
  251. );
  252. $this->expectsAuthentication($origin, $auth);
  253. $this->config->expects($this->once())
  254. ->method('get')
  255. ->with('gitlab-domains')
  256. ->willReturn(array());
  257. $this->io->expects($this->once())
  258. ->method('writeError')
  259. ->with(
  260. 'Using HTTP basic authentication with username "' . $auth['username'] . '"',
  261. true,
  262. IOInterface::DEBUG
  263. );
  264. $expectedHeaders = array_merge(
  265. $headers,
  266. array('Authorization: Basic ' . base64_encode($auth['username'] . ':' . $auth['password']))
  267. );
  268. $this->assertSame(
  269. $expectedHeaders,
  270. $this->authHelper->addAuthenticationHeader($headers, $origin, $url)
  271. );
  272. }
  273. /**
  274. * @dataProvider bitbucketPublicUrlProvider
  275. *
  276. * @param string $url
  277. */
  278. public function testIsPublicBitBucketDownloadWithBitbucketPublicUrl($url)
  279. {
  280. $this->assertTrue($this->authHelper->isPublicBitBucketDownload($url));
  281. }
  282. public function testIsPublicBitBucketDownloadWithNonBitbucketPublicUrl()
  283. {
  284. $this->assertFalse($this->authHelper->isPublicBitBucketDownload(
  285. 'https://bitbucket.org/site/oauth2/authorize')
  286. );
  287. }
  288. public function testStoreAuthAutomatically()
  289. {
  290. $origin = 'github.com';
  291. $storeAuth = true;
  292. $auth = array(
  293. 'username' => 'my_username',
  294. 'password' => 'my_password'
  295. );
  296. /** @var \Composer\Config\ConfigSourceInterface|\PHPUnit_Framework_MockObject_MockObject $configSource */
  297. $configSource = $this
  298. ->getMockBuilder('Composer\Config\ConfigSourceInterface')
  299. ->disableOriginalConstructor()
  300. ->getMock();
  301. $this->config->expects($this->once())
  302. ->method('getAuthConfigSource')
  303. ->willReturn($configSource);
  304. $this->io->expects($this->once())
  305. ->method('getAuthentication')
  306. ->with($origin)
  307. ->willReturn($auth);
  308. $configSource->expects($this->once())
  309. ->method('addConfigSetting')
  310. ->with('http-basic.'.$origin, $auth)
  311. ->willReturn($configSource);
  312. $this->authHelper->storeAuth($origin, $storeAuth);
  313. }
  314. public function testStoreAuthWithPromptYesAnswer()
  315. {
  316. $origin = 'github.com';
  317. $storeAuth = 'prompt';
  318. $auth = array(
  319. 'username' => 'my_username',
  320. 'password' => 'my_password'
  321. );
  322. $answer = 'y';
  323. $configSourceName = 'https://api.gitlab.com/source';
  324. /** @var \Composer\Config\ConfigSourceInterface|\PHPUnit_Framework_MockObject_MockObject $configSource */
  325. $configSource = $this
  326. ->getMockBuilder('Composer\Config\ConfigSourceInterface')
  327. ->disableOriginalConstructor()
  328. ->getMock();
  329. $this->config->expects($this->once())
  330. ->method('getAuthConfigSource')
  331. ->willReturn($configSource);
  332. $configSource->expects($this->once())
  333. ->method('getName')
  334. ->willReturn($configSourceName);
  335. $this->io->expects($this->once())
  336. ->method('askAndValidate')
  337. ->with(
  338. 'Do you want to store credentials for '.$origin.' in '.$configSourceName.' ? [Yn] ',
  339. $this->anything(),
  340. null,
  341. 'y'
  342. )
  343. ->willReturnCallback(function ($question, $validator, $attempts, $default) use ($answer) {
  344. $validator($answer);
  345. return $answer;
  346. });
  347. $this->io->expects($this->once())
  348. ->method('getAuthentication')
  349. ->with($origin)
  350. ->willReturn($auth);
  351. $configSource->expects($this->once())
  352. ->method('addConfigSetting')
  353. ->with('http-basic.'.$origin, $auth)
  354. ->willReturn($configSource);
  355. $this->authHelper->storeAuth($origin, $storeAuth);
  356. }
  357. public function testStoreAuthWithPromptNoAnswer()
  358. {
  359. $origin = 'github.com';
  360. $storeAuth = 'prompt';
  361. $answer = 'n';
  362. $configSourceName = 'https://api.gitlab.com/source';
  363. /** @var \Composer\Config\ConfigSourceInterface|\PHPUnit_Framework_MockObject_MockObject $configSource */
  364. $configSource = $this
  365. ->getMockBuilder('Composer\Config\ConfigSourceInterface')
  366. ->disableOriginalConstructor()
  367. ->getMock();
  368. $this->config->expects($this->once())
  369. ->method('getAuthConfigSource')
  370. ->willReturn($configSource);
  371. $configSource->expects($this->once())
  372. ->method('getName')
  373. ->willReturn($configSourceName);
  374. $this->io->expects($this->once())
  375. ->method('askAndValidate')
  376. ->with(
  377. 'Do you want to store credentials for '.$origin.' in '.$configSourceName.' ? [Yn] ',
  378. $this->anything(),
  379. null,
  380. 'y'
  381. )
  382. ->willReturnCallback(function ($question, $validator, $attempts, $default) use ($answer) {
  383. $validator($answer);
  384. return $answer;
  385. });
  386. $this->authHelper->storeAuth($origin, $storeAuth);
  387. }
  388. /**
  389. * @expectedException RuntimeException
  390. */
  391. public function testStoreAuthWithPromptInvalidAnswer()
  392. {
  393. $origin = 'github.com';
  394. $storeAuth = 'prompt';
  395. $answer = 'invalid';
  396. $configSourceName = 'https://api.gitlab.com/source';
  397. /** @var \Composer\Config\ConfigSourceInterface|\PHPUnit_Framework_MockObject_MockObject $configSource */
  398. $configSource = $this
  399. ->getMockBuilder('Composer\Config\ConfigSourceInterface')
  400. ->disableOriginalConstructor()
  401. ->getMock();
  402. $this->config->expects($this->once())
  403. ->method('getAuthConfigSource')
  404. ->willReturn($configSource);
  405. $configSource->expects($this->once())
  406. ->method('getName')
  407. ->willReturn($configSourceName);
  408. $this->io->expects($this->once())
  409. ->method('askAndValidate')
  410. ->with(
  411. 'Do you want to store credentials for '.$origin.' in '.$configSourceName.' ? [Yn] ',
  412. $this->anything(),
  413. null,
  414. 'y'
  415. )
  416. ->willReturnCallback(function ($question, $validator, $attempts, $default) use ($answer) {
  417. $validator($answer);
  418. return $answer;
  419. });
  420. $this->authHelper->storeAuth($origin, $storeAuth);
  421. }
  422. /**
  423. * @param $origin
  424. * @param $auth
  425. */
  426. private function expectsAuthentication($origin, $auth)
  427. {
  428. $this->io->expects($this->once())
  429. ->method('hasAuthentication')
  430. ->with($origin)
  431. ->willReturn(true);
  432. $this->io->expects($this->once())
  433. ->method('getAuthentication')
  434. ->with($origin)
  435. ->willReturn($auth);
  436. }
  437. }