Bitbucket.php 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  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\Util;
  12. use Composer\Factory;
  13. use Composer\IO\IOInterface;
  14. use Composer\Config;
  15. use Composer\Downloader\TransportException;
  16. /**
  17. * @author Paul Wenke <wenke.paul@gmail.com>
  18. */
  19. class Bitbucket
  20. {
  21. private $io;
  22. private $config;
  23. private $process;
  24. private $remoteFilesystem;
  25. private $token = array();
  26. /**
  27. * Constructor.
  28. *
  29. * @param IOInterface $io The IO instance
  30. * @param Config $config The composer configuration
  31. * @param ProcessExecutor $process Process instance, injectable for mocking
  32. * @param RemoteFilesystem $remoteFilesystem Remote Filesystem, injectable for mocking
  33. */
  34. public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, RemoteFilesystem $remoteFilesystem = null)
  35. {
  36. $this->io = $io;
  37. $this->config = $config;
  38. $this->process = $process ?: new ProcessExecutor;
  39. $this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($this->io, $config);
  40. }
  41. /**
  42. * @return array
  43. */
  44. public function getToken()
  45. {
  46. return $this->token;
  47. }
  48. /**
  49. * Attempts to authorize a Bitbucket domain via OAuth
  50. *
  51. * @param string $originUrl The host this Bitbucket instance is located at
  52. * @return bool true on success
  53. */
  54. public function authorizeOAuth($originUrl)
  55. {
  56. if ($originUrl !== 'bitbucket.org') {
  57. return false;
  58. }
  59. // if available use token from git config
  60. if (0 === $this->process->execute('git config bitbucket.accesstoken', $output)) {
  61. $this->io->setAuthentication($originUrl, 'x-token-auth', trim($output));
  62. return true;
  63. }
  64. return false;
  65. }
  66. /**
  67. * @param string $originUrl
  68. * @return bool
  69. */
  70. private function requestAccessToken($originUrl)
  71. {
  72. try {
  73. $apiUrl = 'https://bitbucket.org/site/oauth2/access_token';
  74. $json = $this->remoteFilesystem->getContents($originUrl, $apiUrl, false, array(
  75. 'retry-auth-failure' => false,
  76. 'http' => array(
  77. 'method' => 'POST',
  78. 'content' => array(
  79. 'grant_type' => 'client_credentials'
  80. )
  81. )
  82. ));
  83. $this->token = json_decode($json, true);
  84. } catch (TransportException $e) {
  85. if (in_array($e->getCode(), array(403, 401))) {
  86. $this->io->writeError('<error>Invalid consumer provided.</error>');
  87. $this->io->writeError('You can also add it manually later by using "composer config bitbucket-oauth.bitbucket.org <consumer-key> <consumer-secret>"');
  88. return false;
  89. }
  90. throw $e;
  91. }
  92. }
  93. /**
  94. * Authorizes a Bitbucket domain interactively via OAuth
  95. *
  96. * @param string $originUrl The host this Bitbucket instance is located at
  97. * @param string $message The reason this authorization is required
  98. * @throws \RuntimeException
  99. * @throws TransportException|\Exception
  100. * @return bool true on success
  101. */
  102. public function authorizeOAuthInteractively($originUrl, $message = null)
  103. {
  104. if ($message) {
  105. $this->io->writeError($message);
  106. }
  107. $url = 'https://confluence.atlassian.com/bitbucket/oauth-on-bitbucket-cloud-238027431.html';
  108. $this->io->writeError(sprintf('Follow the instructions on %s', $url));
  109. $this->io->writeError(sprintf('to create a consumer. It will be stored in "%s" for future use by Composer.', $this->config->getAuthConfigSource()->getName()));
  110. $consumerKey = trim($this->io->askAndHideAnswer('Consumer Key (hidden): '));
  111. if (!$consumerKey) {
  112. $this->io->writeError('<warning>No consumer key given, aborting.</warning>');
  113. $this->io->writeError('You can also add it manually later by using "composer config bitbucket-oauth.bitbucket.org <consumer-key> <consumer-secret>"');
  114. return false;
  115. }
  116. $consumerSecret = trim($this->io->askAndHideAnswer('Consumer Secret (hidden): '));
  117. if (!$consumerSecret) {
  118. $this->io->writeError('<warning>No consumer secret given, aborting.</warning>');
  119. $this->io->writeError('You can also add it manually later by using "composer config bitbucket-oauth.bitbucket.org <consumer-key> <consumer-secret>"');
  120. return false;
  121. }
  122. $this->io->setAuthentication($originUrl, $consumerKey, $consumerSecret);
  123. $this->requestAccessToken($originUrl);
  124. // store value in user config
  125. $this->config->getConfigSource()->removeConfigSetting('bitbucket-oauth.'.$originUrl);
  126. $consumer = array(
  127. "consumer-key" => $consumerKey,
  128. "consumer-secret" => $consumerSecret
  129. );
  130. $this->config->getAuthConfigSource()->addConfigSetting('bitbucket-oauth.'.$originUrl, $consumer);
  131. $this->io->writeError('<info>Consumer stored successfully.</info>');
  132. return true;
  133. }
  134. /**
  135. * Retrieves an access token from Bitbucket.
  136. *
  137. * @param string $originUrl
  138. * @param string $consumerKey
  139. * @param string $consumerSecret
  140. * @return array
  141. */
  142. public function requestToken($originUrl, $consumerKey, $consumerSecret)
  143. {
  144. if (!empty($this->token)) {
  145. return $this->token;
  146. }
  147. $this->io->setAuthentication($originUrl, $consumerKey, $consumerSecret);
  148. $this->requestAccessToken($originUrl);
  149. return $this->token;
  150. }
  151. }