BitbucketTest.php 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  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\Util\Bitbucket;
  13. /**
  14. * @author Paul Wenke <wenke.paul@gmail.com>
  15. */
  16. class BitbucketTest extends \PHPUnit_Framework_TestCase
  17. {
  18. private $username = 'username';
  19. private $password = 'password';
  20. private $consumer_key = 'consumer_key';
  21. private $consumer_secret = 'consumer_secret';
  22. private $message = 'mymessage';
  23. private $origin = 'bitbucket.org';
  24. private $token = 'bitbuckettoken';
  25. /** @type \Composer\IO\ConsoleIO|\PHPUnit_Framework_MockObject_MockObject */
  26. private $io;
  27. /** @type \Composer\Util\RemoteFilesystem|\PHPUnit_Framework_MockObject_MockObject */
  28. private $rfs;
  29. /** @type \Composer\Config|\PHPUnit_Framework_MockObject_MockObject */
  30. private $config;
  31. /** @type Bitbucket */
  32. private $bitbucket;
  33. /** @var int */
  34. private $time;
  35. protected function setUp()
  36. {
  37. $this->io = $this
  38. ->getMockBuilder('Composer\IO\ConsoleIO')
  39. ->disableOriginalConstructor()
  40. ->getMock()
  41. ;
  42. $this->rfs = $this
  43. ->getMockBuilder('Composer\Util\RemoteFilesystem')
  44. ->disableOriginalConstructor()
  45. ->getMock()
  46. ;
  47. $this->config = $this->getMock('Composer\Config');
  48. $this->time = time();
  49. $this->bitbucket = new Bitbucket($this->io, $this->config, null, $this->rfs, $this->time);
  50. }
  51. public function testRequestAccessTokenWithValidOAuthConsumer()
  52. {
  53. $this->io->expects($this->once())
  54. ->method('setAuthentication')
  55. ->with($this->origin, $this->consumer_key, $this->consumer_secret);
  56. $this->rfs->expects($this->once())
  57. ->method('getContents')
  58. ->with(
  59. $this->origin,
  60. Bitbucket::OAUTH2_ACCESS_TOKEN_URL,
  61. false,
  62. array(
  63. 'retry-auth-failure' => false,
  64. 'http' => array(
  65. 'method' => 'POST',
  66. 'content' => 'grant_type=client_credentials',
  67. ),
  68. )
  69. )
  70. ->willReturn(
  71. sprintf(
  72. '{"access_token": "%s", "scopes": "repository", "expires_in": 3600, "refresh_token": "refreshtoken", "token_type": "bearer"}',
  73. $this->token
  74. )
  75. );
  76. $this->config->expects($this->once())
  77. ->method('get')
  78. ->with('bitbucket-oauth')
  79. ->willReturn(null);
  80. $this->setExpectationsForStoringAccessToken();
  81. $this->assertEquals(
  82. $this->token,
  83. $this->bitbucket->requestToken($this->origin, $this->consumer_key, $this->consumer_secret)
  84. );
  85. }
  86. public function testRequestAccessTokenWithValidOAuthConsumerAndValidStoredAccessToken()
  87. {
  88. $this->config->expects($this->once())
  89. ->method('get')
  90. ->with('bitbucket-oauth')
  91. ->willReturn(
  92. array(
  93. $this->origin => array(
  94. 'access-token' => $this->token,
  95. 'access-token-expiration' => $this->time + 1800,
  96. 'consumer-key' => $this->consumer_key,
  97. 'consumer-secret' => $this->consumer_secret,
  98. ),
  99. )
  100. );
  101. $this->assertEquals(
  102. $this->token,
  103. $this->bitbucket->requestToken($this->origin, $this->consumer_key, $this->consumer_secret)
  104. );
  105. }
  106. public function testRequestAccessTokenWithValidOAuthConsumerAndExpiredAccessToken()
  107. {
  108. $this->config->expects($this->once())
  109. ->method('get')
  110. ->with('bitbucket-oauth')
  111. ->willReturn(
  112. array(
  113. $this->origin => array(
  114. 'access-token' => 'randomExpiredToken',
  115. 'access-token-expiration' => $this->time - 400,
  116. 'consumer-key' => $this->consumer_key,
  117. 'consumer-secret' => $this->consumer_secret,
  118. ),
  119. )
  120. );
  121. $this->io->expects($this->once())
  122. ->method('setAuthentication')
  123. ->with($this->origin, $this->consumer_key, $this->consumer_secret);
  124. $this->rfs->expects($this->once())
  125. ->method('getContents')
  126. ->with(
  127. $this->origin,
  128. Bitbucket::OAUTH2_ACCESS_TOKEN_URL,
  129. false,
  130. array(
  131. 'retry-auth-failure' => false,
  132. 'http' => array(
  133. 'method' => 'POST',
  134. 'content' => 'grant_type=client_credentials',
  135. ),
  136. )
  137. )
  138. ->willReturn(
  139. sprintf(
  140. '{"access_token": "%s", "scopes": "repository", "expires_in": 3600, "refresh_token": "refreshtoken", "token_type": "bearer"}',
  141. $this->token
  142. )
  143. );
  144. $this->setExpectationsForStoringAccessToken();
  145. $this->assertEquals(
  146. $this->token,
  147. $this->bitbucket->requestToken($this->origin, $this->consumer_key, $this->consumer_secret)
  148. );
  149. }
  150. public function testRequestAccessTokenWithUsernameAndPassword()
  151. {
  152. $this->io->expects($this->once())
  153. ->method('setAuthentication')
  154. ->with($this->origin, $this->username, $this->password);
  155. $this->io->expects($this->any())
  156. ->method('writeError')
  157. ->withConsecutive(
  158. array('<error>Invalid OAuth consumer provided.</error>'),
  159. array('This can have two reasons:'),
  160. array('1. You are authenticating with a bitbucket username/password combination'),
  161. array('2. You are using an OAuth consumer, but didn\'t configure a (dummy) callback url')
  162. );
  163. $this->rfs->expects($this->once())
  164. ->method('getContents')
  165. ->with(
  166. $this->origin,
  167. Bitbucket::OAUTH2_ACCESS_TOKEN_URL,
  168. false,
  169. array(
  170. 'retry-auth-failure' => false,
  171. 'http' => array(
  172. 'method' => 'POST',
  173. 'content' => 'grant_type=client_credentials',
  174. ),
  175. )
  176. )
  177. ->willThrowException(
  178. new \Composer\Downloader\TransportException(
  179. sprintf(
  180. 'The \'%s\' URL could not be accessed: HTTP/1.1 400 BAD REQUEST',
  181. Bitbucket::OAUTH2_ACCESS_TOKEN_URL
  182. ),
  183. 400
  184. )
  185. );
  186. $this->config->expects($this->once())
  187. ->method('get')
  188. ->with('bitbucket-oauth')
  189. ->willReturn(null);
  190. $this->assertEquals('', $this->bitbucket->requestToken($this->origin, $this->username, $this->password));
  191. }
  192. public function testUsernamePasswordAuthenticationFlow()
  193. {
  194. $this->io
  195. ->expects($this->at(0))
  196. ->method('writeError')
  197. ->with($this->message)
  198. ;
  199. $this->io->expects($this->exactly(2))
  200. ->method('askAndHideAnswer')
  201. ->withConsecutive(
  202. array('Consumer Key (hidden): '),
  203. array('Consumer Secret (hidden): ')
  204. )
  205. ->willReturnOnConsecutiveCalls($this->consumer_key, $this->consumer_secret);
  206. $this->rfs
  207. ->expects($this->once())
  208. ->method('getContents')
  209. ->with(
  210. $this->equalTo($this->origin),
  211. $this->equalTo(sprintf('https://%s/site/oauth2/access_token', $this->origin)),
  212. $this->isFalse(),
  213. $this->anything()
  214. )
  215. ->willReturn(
  216. sprintf(
  217. '{"access_token": "%s", "scopes": "repository", "expires_in": 3600, "refresh_token": "refresh_token", "token_type": "bearer"}',
  218. $this->token
  219. )
  220. )
  221. ;
  222. $this->setExpectationsForStoringAccessToken(true);
  223. $this->assertTrue($this->bitbucket->authorizeOAuthInteractively($this->origin, $this->message));
  224. }
  225. private function setExpectationsForStoringAccessToken($removeBasicAuth = false)
  226. {
  227. $configSourceMock = $this->getMock('Composer\Config\ConfigSourceInterface');
  228. $this->config->expects($this->once())
  229. ->method('getConfigSource')
  230. ->willReturn($configSourceMock);
  231. $configSourceMock->expects($this->once())
  232. ->method('removeConfigSetting')
  233. ->with('bitbucket-oauth.' . $this->origin);
  234. $authConfigSourceMock = $this->getMock('Composer\Config\ConfigSourceInterface');
  235. $this->config->expects($this->atLeastOnce())
  236. ->method('getAuthConfigSource')
  237. ->willReturn($authConfigSourceMock);
  238. $authConfigSourceMock->expects($this->once())
  239. ->method('addConfigSetting')
  240. ->with(
  241. 'bitbucket-oauth.' . $this->origin,
  242. array(
  243. "consumer-key" => $this->consumer_key,
  244. "consumer-secret" => $this->consumer_secret,
  245. "access-token" => $this->token,
  246. "access-token-expiration" => $this->time + 3600,
  247. )
  248. );
  249. if ($removeBasicAuth) {
  250. $authConfigSourceMock->expects($this->once())
  251. ->method('removeConfigSetting')
  252. ->with('http-basic.' . $this->origin);
  253. }
  254. }
  255. }