GitLab.php 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  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\IO\IOInterface;
  13. use Composer\Config;
  14. use Composer\Factory;
  15. use Composer\Downloader\TransportException;
  16. use Composer\Json\JsonFile;
  17. /**
  18. * @author Roshan Gautam <roshan.gautam@hotmail.com>
  19. */
  20. class GitLab
  21. {
  22. protected $io;
  23. protected $config;
  24. protected $process;
  25. protected $remoteFilesystem;
  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. * Attempts to authorize a GitLab domain via OAuth.
  43. *
  44. * @param string $originUrl The host this GitLab instance is located at
  45. *
  46. * @return bool true on success
  47. */
  48. public function authorizeOAuth($originUrl)
  49. {
  50. if (!in_array($originUrl, $this->config->get('gitlab-domains'), true)) {
  51. return false;
  52. }
  53. // if available use token from git config
  54. if (0 === $this->process->execute('git config gitlab.accesstoken', $output)) {
  55. $this->io->setAuthentication($originUrl, trim($output), 'oauth2');
  56. return true;
  57. }
  58. return false;
  59. }
  60. /**
  61. * Authorizes a GitLab domain interactively via OAuth.
  62. *
  63. * @param string $scheme Scheme used in the origin URL
  64. * @param string $originUrl The host this GitLab instance is located at
  65. * @param string $message The reason this authorization is required
  66. *
  67. * @throws \RuntimeException
  68. * @throws TransportException|\Exception
  69. *
  70. * @return bool true on success
  71. */
  72. public function authorizeOAuthInteractively($scheme, $originUrl, $message = null)
  73. {
  74. if ($message) {
  75. $this->io->writeError($message);
  76. }
  77. $this->io->writeError(sprintf('A token will be created and stored in "%s", your password will never be stored', $this->config->getAuthConfigSource()->getName()));
  78. $this->io->writeError('To revoke access to this token you can visit '.$originUrl.'/profile/applications');
  79. $attemptCounter = 0;
  80. while ($attemptCounter++ < 5) {
  81. try {
  82. $response = $this->createToken($scheme, $originUrl);
  83. } catch (TransportException $e) {
  84. // 401 is bad credentials,
  85. // 403 is max login attempts exceeded
  86. if (in_array($e->getCode(), array(403, 401))) {
  87. if (401 === $e->getCode()) {
  88. $this->io->writeError('Bad credentials.');
  89. } else {
  90. $this->io->writeError('Maximum number of login attempts exceeded. Please try again later.');
  91. }
  92. $this->io->writeError('You can also manually create a personal token at '.$scheme.'://'.$originUrl.'/profile/applications');
  93. $this->io->writeError('Add it using "composer config gitlab-oauth.'.$originUrl.' <token>"');
  94. continue;
  95. }
  96. throw $e;
  97. }
  98. $this->io->setAuthentication($originUrl, $response['access_token'], 'oauth2');
  99. // store value in user config in auth file
  100. $this->config->getAuthConfigSource()->addConfigSetting('gitlab-oauth.'.$originUrl, $response['access_token']);
  101. return true;
  102. }
  103. throw new \RuntimeException('Invalid GitLab credentials 5 times in a row, aborting.');
  104. }
  105. private function createToken($scheme, $originUrl)
  106. {
  107. $username = $this->io->ask('Username: ');
  108. $password = $this->io->askAndHideAnswer('Password: ');
  109. $headers = array('Content-Type: application/x-www-form-urlencoded');
  110. $apiUrl = $originUrl;
  111. $data = http_build_query(array(
  112. 'username' => $username,
  113. 'password' => $password,
  114. 'grant_type' => 'password',
  115. ));
  116. $options = array(
  117. 'retry-auth-failure' => false,
  118. 'http' => array(
  119. 'method' => 'POST',
  120. 'header' => $headers,
  121. 'content' => $data,
  122. ),
  123. );
  124. $json = $this->remoteFilesystem->getContents($originUrl, $scheme.'://'.$apiUrl.'/oauth/token', false, $options);
  125. $this->io->writeError('Token successfully created');
  126. return JsonFile::parseJson($json);
  127. }
  128. }