Browse Source

Allows SSH urls for gitlab and detect the scheme

SSH urls uses HTTPS to request the API
Jérôme Tamarelle 9 years ago
parent
commit
4255db9e31

+ 9 - 9
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;

+ 4 - 28
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);

+ 6 - 6
src/Composer/Util/GitLab.php

@@ -4,7 +4,7 @@
  * This file is part of Composer.
  *
  * (c) Roshan Gautam <roshan.gautam@hotmail.com>
- *     
+ *
  *
  * 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.' <token>"');
 
                     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');
 

+ 2 - 1
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);
             }

+ 26 - 20
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 = <<<JSON
 {
@@ -74,11 +83,10 @@ JSON;
         return $driver;
     }
 
-    /**
-     * @depends testInitialize
-     */
-    public function testGetDist(GitLabDriver $driver)
+    public function testGetDist()
     {
+        $driver = $this->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),
         );
     }

+ 2 - 2
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()