From 4255db9e3195ad973d2571fa78bdf3af09203b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Sat, 14 Nov 2015 16:00:14 +0100 Subject: [PATCH] Allows SSH urls for gitlab and detect the scheme SSH urls uses HTTPS to request the API --- src/Composer/Repository/Vcs/GitLabDriver.php | 18 ++++---- src/Composer/Util/Git.php | 32 ++----------- src/Composer/Util/GitLab.php | 12 ++--- src/Composer/Util/RemoteFilesystem.php | 3 +- .../Test/Repository/Vcs/GitLabDriverTest.php | 46 +++++++++++-------- tests/Composer/Test/Util/GitLabTest.php | 4 +- 6 files changed, 49 insertions(+), 66 deletions(-) diff --git a/src/Composer/Repository/Vcs/GitLabDriver.php b/src/Composer/Repository/Vcs/GitLabDriver.php index 19908374e..d50ea1f37 100644 --- a/src/Composer/Repository/Vcs/GitLabDriver.php +++ b/src/Composer/Repository/Vcs/GitLabDriver.php @@ -63,20 +63,20 @@ class GitLabDriver extends VcsDriver /** * Extracts information from the repository url. - * SSH urls are not supported in order to know the HTTP sheme to use. + * SSH urls uses https by default. * * {@inheritDoc} */ public function initialize() { - if (!preg_match('#^(https?)://([^/]+)/([^/]+)/([^/]+)(?:\.git|/)?$#', $this->url, $match)) { + if (!preg_match('#^((https?)://([^/]+)/|git@([^:]+):)([^/]+)/(.+?)(?:\.git|/)?$#', $this->url, $match)) { throw new \InvalidArgumentException('The URL provided is invalid. It must be the HTTP URL of a GitLab project.'); } - $this->scheme = $match[1]; - $this->originUrl = $match[2]; - $this->owner = $match[3]; - $this->repository = preg_replace('#(\.git)$#', '', $match[4]); + $this->scheme = !empty($match[2]) ? $match[2] : 'https'; + $this->originUrl = !empty($match[3]) ? $match[3] : $match[4]; + $this->owner = $match[5]; + $this->repository = preg_replace('#(\.git)$#', '', $match[6]); $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->owner.'/'.$this->repository); @@ -343,12 +343,12 @@ class GitLabDriver extends VcsDriver */ public static function supports(IOInterface $io, Config $config, $url, $deep = false) { - if (!preg_match('#^(https?)://([^/]+)/([^/]+)/([^/]+)(?:\.git|/)?$#', $url, $match)) { + if (!preg_match('#^((https?)://([^/]+)/|git@([^:]+):)([^/]+)/(.+?)(?:\.git|/)?$#', $url, $match)) { return false; } - $scheme = $match[1]; - $originUrl = $match[2]; + $scheme = !empty($match[2]) ? $match[2] : 'https'; + $originUrl = !empty($match[3]) ? $match[3] : $match[4]; if (!in_array($originUrl, (array) $config->get('gitlab-domains'))) { return false; diff --git a/src/Composer/Util/Git.php b/src/Composer/Util/Git.php index a16fa3397..e0b9024f9 100644 --- a/src/Composer/Util/Git.php +++ b/src/Composer/Util/Git.php @@ -80,16 +80,15 @@ class Git $this->throwException('Failed to clone ' . self::sanitizeUrl($url) .' via '.implode(', ', $protocols).' protocols, aborting.' . "\n\n" . implode("\n", $messages), $url); } - // if we have a private github/gitlab url and the ssh protocol is disabled then we skip it and directly fallback to https + // if we have a private github url and the ssh protocol is disabled then we skip it and directly fallback to https $bypassSshForGitHub = preg_match('{^git@'.self::getGitHubDomainsRegex($this->config).':(.+?)\.git$}i', $url) && !in_array('ssh', $protocols, true); - $bypassSshForGitLab = preg_match('{^git@'.self::getGitLabDomainsRegex($this->config).':(.+?)\.git$}i', $url) && !in_array('ssh', $protocols, true); $command = call_user_func($commandCallable, $url); - if ($bypassSshForGitHub || $bypassSshForGitLab || 0 !== $this->process->execute($command, $ignoredOutput, $cwd)) { - // private github/gitlab repository without git access, try https with auth + if ($bypassSshForGitHub || 0 !== $this->process->execute($command, $ignoredOutput, $cwd)) { + // private github repository without git access, try https with auth if (preg_match('{^git@'.self::getGitHubDomainsRegex($this->config).':(.+?)\.git$}i', $url, $match)) { - + if (!$this->io->hasAuthentication($match[1])) { $gitHubUtil = new GitHub($this->io, $this->config, $this->process); $message = 'Cloning failed using an ssh key for authentication, enter your GitHub credentials to access private repos'; @@ -107,24 +106,6 @@ class Git return; } } - } elseif (preg_match('{^git@'.self::getGitLabDomainsRegex($this->config).':(.+?)\.git$}i', $url, $match)) { - if (!$this->io->hasAuthentication($match[1])) { - $gitLabUtil = new GitLab($this->io, $this->config, $this->process); - $message = 'Cloning failed using an ssh key for authentication, enter your GitHub credentials to access private repos'; - - if (!$gitLabUtil->authorizeOAuth($match[1]) && $this->io->isInteractive()) { - $gitLabUtil->authorizeOAuthInteractively($match[1], $message); - } - } - - if ($this->io->hasAuthentication($match[1])) { - $auth = $this->io->getAuthentication($match[1]); - $url = 'http://oauth2:' . rawurlencode($auth['username']) . '@'.$match[1].'/'.$match[2].'.git'; - $command = call_user_func($commandCallable, $url); - if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) { - return; - } - } } elseif ($this->isAuthenticationFailure($url, $match)) { // private non-github repo that failed to authenticate if (strpos($match[2], '@')) { list($authParts, $match[2]) = explode('@', $match[2], 2); @@ -225,11 +206,6 @@ class Git return '('.implode('|', array_map('preg_quote', $config->get('github-domains'))).')'; } - public static function getGitLabDomainsRegex(Config $config) - { - return '('.implode('|', array_map('preg_quote', $config->get('gitlab-domains'))).')'; - } - public static function sanitizeUrl($message) { return preg_replace('{://([^@]+?):.+?@}', '://$1:***@', $message); diff --git a/src/Composer/Util/GitLab.php b/src/Composer/Util/GitLab.php index c94be6121..499cc492d 100644 --- a/src/Composer/Util/GitLab.php +++ b/src/Composer/Util/GitLab.php @@ -4,7 +4,7 @@ * This file is part of Composer. * * (c) Roshan Gautam - * + * * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -77,7 +77,7 @@ class GitLab * * @return bool true on success */ - public function authorizeOAuthInteractively($originUrl, $message = null) + public function authorizeOAuthInteractively($scheme, $originUrl, $message = null) { if ($message) { $this->io->writeError($message); @@ -90,7 +90,7 @@ class GitLab while ($attemptCounter++ < 5) { try { - $response = $this->createToken($originUrl); + $response = $this->createToken($scheme, $originUrl); } catch (TransportException $e) { // 401 is bad credentials, // 403 is max login attempts exceeded @@ -101,7 +101,7 @@ class GitLab $this->io->writeError('Maximum number of login attempts exceeded. Please try again later.'); } - $this->io->writeError('You can also manually create a personal token at '.$originUrl.'/profile/applications'); + $this->io->writeError('You can also manually create a personal token at '.$scheme.'://'.$originUrl.'/profile/applications'); $this->io->writeError('Add it using "composer config gitlab-oauth.'.$originUrl.' "'); continue; @@ -121,7 +121,7 @@ class GitLab throw new \RuntimeException('Invalid GitLab credentials 5 times in a row, aborting.'); } - private function createToken($originUrl) + private function createToken($scheme, $originUrl) { $username = $this->io->ask('Username: '); $password = $this->io->askAndHideAnswer('Password: '); @@ -143,7 +143,7 @@ class GitLab ), ); - $json = $this->remoteFilesystem->getContents($originUrl, 'http://'.$apiUrl.'/oauth/token', false, $options); + $json = $this->remoteFilesystem->getContents($originUrl, $scheme.'://'.$apiUrl.'/oauth/token', false, $options); $this->io->writeError('Token successfully created'); diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index af42181bb..fe9c114c1 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -124,6 +124,7 @@ class RemoteFilesystem $originUrl = 'github.com'; } + $this->scheme = parse_url($fileUrl, PHP_URL_SCHEME); $this->bytesMax = 0; $this->originUrl = $originUrl; $this->fileUrl = $fileUrl; @@ -415,7 +416,7 @@ class RemoteFilesystem $message = "\n".'Could not fetch '.$this->fileUrl.', enter your ' . $this->originUrl . ' credentials ' .($httpStatus === 401 ? 'to access private repos' : 'to go over the API rate limit'); $gitLabUtil = new GitLab($this->io, $this->config, null); if (!$gitLabUtil->authorizeOAuth($this->originUrl) - && (!$this->io->isInteractive() || !$gitLabUtil->authorizeOAuthInteractively($this->originUrl, $message)) + && (!$this->io->isInteractive() || !$gitLabUtil->authorizeOAuthInteractively($this->scheme, $this->originUrl, $message)) ) { throw new TransportException('Could not authenticate against '.$this->originUrl, 401); } diff --git a/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php index 44a6c2fe6..d5d6ed832 100644 --- a/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php @@ -36,11 +36,20 @@ class GitLabDriverTest extends \PHPUnit_Framework_TestCase $this->remoteFilesystem = $this->prophesize('Composer\Util\RemoteFilesystem'); } - public function testInitialize() + public function getInitializeUrls() { - $url = 'https://gitlab.com/mygroup/myproject'; - $apiUrl = 'https://gitlab.com/api/v3/projects/mygroup%2Fmyproject'; + return array( + array('https://gitlab.com/mygroup/myproject', 'https://gitlab.com/api/v3/projects/mygroup%2Fmyproject'), + array('http://gitlab.com/mygroup/myproject', 'http://gitlab.com/api/v3/projects/mygroup%2Fmyproject'), + array('git@gitlab.com:mygroup/myproject', 'https://gitlab.com/api/v3/projects/mygroup%2Fmyproject'), + ); + } + /** + * @dataProvider getInitializeUrls + */ + public function testInitialize($url, $apiUrl) + { // @link http://doc.gitlab.com/ce/api/projects.html#get-single-project $projectData = <<testInitialize('https://gitlab.com/mygroup/myproject', 'https://gitlab.com/api/v3/projects/mygroup%2Fmyproject'); + $reference = 'c3ebdbf9cceddb82cd2089aaef8c7b992e536363'; $expected = array( 'type' => 'zip', @@ -90,11 +98,10 @@ JSON; $this->assertEquals($expected, $driver->getDist($reference)); } - /** - * @depends testInitialize - */ - public function testGetSource(GitLabDriver $driver) + public function testGetSource() { + $driver = $this->testInitialize('https://gitlab.com/mygroup/myproject', 'https://gitlab.com/api/v3/projects/mygroup%2Fmyproject'); + $reference = 'c3ebdbf9cceddb82cd2089aaef8c7b992e536363'; $expected = array( 'type' => 'git', @@ -105,11 +112,10 @@ JSON; $this->assertEquals($expected, $driver->getSource($reference)); } - /** - * @depends testInitialize - */ - public function testGetTags(GitLabDriver $driver) + public function testGetTags() { + $driver = $this->testInitialize('https://gitlab.com/mygroup/myproject', 'https://gitlab.com/api/v3/projects/mygroup%2Fmyproject'); + $apiUrl = 'https://gitlab.com/api/v3/projects/mygroup%2Fmyproject/repository/tags'; // @link http://doc.gitlab.com/ce/api/repositories.html#list-project-repository-tags @@ -148,11 +154,10 @@ JSON; $this->assertEquals($expected, $driver->getTags(), 'Tags are cached'); } - /** - * @depends testInitialize - */ - public function testGetBranches(GitLabDriver $driver) + public function testGetBranches() { + $driver = $this->testInitialize('https://gitlab.com/mygroup/myproject', 'https://gitlab.com/api/v3/projects/mygroup%2Fmyproject'); + $apiUrl = 'https://gitlab.com/api/v3/projects/mygroup%2Fmyproject/repository/branches'; // @link http://doc.gitlab.com/ce/api/repositories.html#list-project-repository-branches @@ -207,7 +212,8 @@ JSON; array('http://gitlab.com/foo/bar.git', true), array('http://gitlab.com/foo/bar.baz.git', true), array('https://gitlab.com/foo/bar', extension_loaded('openssl')), // Platform requirement - array('git@gitlab.com:foo/bar.git', false), + array('git@gitlab.com:foo/bar.git', extension_loaded('openssl')), + array('git@example.com:foo/bar.git', false), array('http://example.com/foo/bar', false), ); } diff --git a/tests/Composer/Test/Util/GitLabTest.php b/tests/Composer/Test/Util/GitLabTest.php index 31ac52080..2c5e62527 100644 --- a/tests/Composer/Test/Util/GitLabTest.php +++ b/tests/Composer/Test/Util/GitLabTest.php @@ -70,7 +70,7 @@ class GitLabTest extends \PHPUnit_Framework_TestCase $gitLab = new GitLab($io, $config, null, $rfs); - $this->assertTrue($gitLab->authorizeOAuthInteractively($this->origin, $this->message)); + $this->assertTrue($gitLab->authorizeOAuthInteractively('http', $this->origin, $this->message)); } /** @@ -109,7 +109,7 @@ class GitLabTest extends \PHPUnit_Framework_TestCase $gitLab = new GitLab($io, $config, null, $rfs); - $gitLab->authorizeOAuthInteractively($this->origin); + $gitLab->authorizeOAuthInteractively('https', $this->origin); } private function getIOMock()