diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 508a76afa..c5ec96673 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -80,7 +80,11 @@ class FileDownloader implements DownloaderInterface if (404 === $e->getCode() && 'github.com' === parse_url($processUrl, PHP_URL_HOST)) { $message = "\n".'Could not fetch '.$processUrl.', enter your GitHub credentials to access private repos'; $gitHubUtil = new GitHub($this->io, $this->config, null, $this->rfs); - $gitHubUtil->authorizeOAuth('github.com', $message); + if (!$gitHubUtil->authorizeOAuth('github.com') + && (!$this->io->isInteractive() || !$gitHubUtil->authorizeOAuthInteractively('github.com', $message)) + ) { + throw $e; + } $this->rfs->copy(parse_url($processUrl, PHP_URL_HOST), $processUrl, $fileName); } else { throw $e; diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index c80b57634..c7c1774b8 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -285,19 +285,24 @@ class GitDownloader extends VcsDownloader $command = call_user_func($commandCallable, $url); if (0 !== $this->process->execute($command, $handler)) { // private github repository without git access, try https with auth - if (preg_match('{^git@(github.com):(.+?)\.git$}i', $url, $match) && $this->io->isInteractive()) { + if (preg_match('{^git@(github.com):(.+?)\.git$}i', $url, $match)) { if (!$this->io->hasAuthorization($match[1])) { - $message = 'Cloning failed using an ssh key for authentication, enter your GitHub credentials to access private repos'; $gitHubUtil = new GitHub($this->io, $this->config, $this->process); - $gitHubUtil->authorizeOAuth($match[1], $message); + $message = 'Cloning failed using an ssh key for authentication, enter your GitHub credentials to access private repos'; + + if (!$gitHubUtil->authorizeOAuth($match[1]) && $this->io->isInteractive()) { + $gitHubUtil->authorizeOAuthInteractively($match[1], $message); + } } - $auth = $this->io->getAuthorization($match[1]); - $url = 'https://'.$auth['username'] . ':' . $auth['password'] . '@'.$match[1].'/'.$match[2].'.git'; + if ($this->io->hasAuthorization($match[1])) { + $auth = $this->io->getAuthorization($match[1]); + $url = 'https://'.$auth['username'] . ':' . $auth['password'] . '@'.$match[1].'/'.$match[2].'.git'; - $command = call_user_func($commandCallable, $url); - if (0 === $this->process->execute($command, $handler)) { - return; + $command = call_user_func($commandCallable, $url); + if (0 === $this->process->execute($command, $handler)) { + return; + } } } diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index 7c53970cd..fea55c462 100755 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -244,6 +244,8 @@ class GitHubDriver extends VcsDriver try { return parent::getContents($url); } catch (TransportException $e) { + $gitHubUtil = new GitHub($this->io, $this->config, $this->process, $this->remoteFilesystem); + switch ($e->getCode()) { case 401: case 404: @@ -252,17 +254,25 @@ class GitHubDriver extends VcsDriver throw $e; } - if (!$this->io->isInteractive()) { - return $this->attemptCloneFallback($e); + if ($gitHubUtil->authorizeOAuth($this->originUrl)) { + return parent::getContents($url); } - $this->authorizeOAuth('Your GitHub credentials are required to fetch private repository metadata ('.$this->url.')'); + if (!$this->io->isInteractive()) { + return $this->attemptCloneFallback(); + } + + $gitHubUtil->authorizeOAuthInteractively($this->originUrl, 'Your GitHub credentials are required to fetch private repository metadata ('.$this->url.')'); return parent::getContents($url); case 403: + if (!$this->io->hasAuthorization($this->originUrl) && $gitHubUtil->authorizeOAuth($this->originUrl)) { + return parent::getContents($url); + } + if (!$this->io->isInteractive() && $fetchingRepoData) { - return $this->attemptCloneFallback($e); + return $this->attemptCloneFallback(); } $rateLimited = false; @@ -278,7 +288,7 @@ class GitHubDriver extends VcsDriver throw $e; } - $this->authorizeOAuth('API limit exhausted. Enter your GitHub credentials to get a larger API limit ('.$this->url.')'); + $gitHubUtil->authorizeOAuthInteractively($this->originUrl, 'API limit exhausted. Enter your GitHub credentials to get a larger API limit ('.$this->url.')'); return parent::getContents($url); } @@ -346,10 +356,4 @@ class GitHubDriver extends VcsDriver throw $e; } } - - protected function authorizeOAuth($message) - { - $gitHubUtil = new GitHub($this->io, $this->config, $this->process, $this->remoteFilesystem); - $gitHubUtil->authorizeOAuth($this->originUrl, $message); - } } diff --git a/src/Composer/Util/GitHub.php b/src/Composer/Util/GitHub.php index ba5fcd351..33e5443f7 100644 --- a/src/Composer/Util/GitHub.php +++ b/src/Composer/Util/GitHub.php @@ -44,20 +44,36 @@ class GitHub } /** - * Authorizes a GitHub domain via OAuth + * Attempts to authorize a GitHub domain via OAuth * * @param string $originUrl The host this GitHub instance is located at - * @param string $message The reason this authorization is required + * @return bool true on success */ - public function authorizeOAuth($originUrl, $message = null) + public function authorizeOAuth($originUrl) { + if ('github.com' !== $originUrl) { + return false; + } + // if available use token from git config if (0 === $this->process->execute('git config github.accesstoken', $output)) { $this->io->setAuthorization($originUrl, trim($output), 'x-oauth-basic'); - return; + return true; } + return false; + } + + /** + * Authorizes a GitHub domain interactively via OAuth + * + * @param string $originUrl The host this GitHub instance is located at + * @param string $message The reason this authorization is required + * @return bool true on success + */ + public function authorizeOAuthInteractively($originUrl, $message = null) + { $attemptCounter = 0; if ($message) { @@ -105,7 +121,7 @@ class GitHub $githubTokens[$originUrl] = $contents['token']; $this->config->getConfigSource()->addConfigSetting('github-oauth', $githubTokens); - return; + return true; } throw new \RuntimeException("Invalid GitHub credentials 5 times in a row, aborting."); diff --git a/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php index df8fcc6a5..ccde42a00 100644 --- a/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php @@ -264,30 +264,35 @@ class GitHubDriverTest extends \PHPUnit_Framework_TestCase $process->expects($this->at(0)) ->method('execute') - ->with($this->stringContains($repoSshUrl)) - ->will($this->returnValue(0)); + ->with($this->equalTo('git config github.accesstoken')) + ->will($this->returnValue(1)); $process->expects($this->at(1)) ->method('execute') - ->with($this->stringContains('git tag')); + ->with($this->stringContains($repoSshUrl)) + ->will($this->returnValue(0)); $process->expects($this->at(2)) + ->method('execute') + ->with($this->stringContains('git tag')); + + $process->expects($this->at(3)) ->method('splitLines') ->will($this->returnValue(array($identifier))); - $process->expects($this->at(3)) + $process->expects($this->at(4)) ->method('execute') ->with($this->stringContains('git branch --no-color --no-abbrev -v')); - $process->expects($this->at(4)) + $process->expects($this->at(5)) ->method('splitLines') ->will($this->returnValue(array(' test_master edf93f1fccaebd8764383dc12016d0a1a9672d89 Fix test & behavior'))); - $process->expects($this->at(5)) + $process->expects($this->at(6)) ->method('execute') ->with($this->stringContains('git branch --no-color')); - $process->expects($this->at(6)) + $process->expects($this->at(7)) ->method('splitLines') ->will($this->returnValue(array('* test_master')));