BitbucketTest.php 9.9 KB

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