Allows SSH urls for gitlab and detect the scheme
SSH urls uses HTTPS to request the APIpull/3765/head
parent
ac68a721f4
commit
4255db9e31
|
@ -63,20 +63,20 @@ class GitLabDriver extends VcsDriver
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts information from the repository url.
|
* 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}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function initialize()
|
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.');
|
throw new \InvalidArgumentException('The URL provided is invalid. It must be the HTTP URL of a GitLab project.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->scheme = $match[1];
|
$this->scheme = !empty($match[2]) ? $match[2] : 'https';
|
||||||
$this->originUrl = $match[2];
|
$this->originUrl = !empty($match[3]) ? $match[3] : $match[4];
|
||||||
$this->owner = $match[3];
|
$this->owner = $match[5];
|
||||||
$this->repository = preg_replace('#(\.git)$#', '', $match[4]);
|
$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);
|
$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)
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$scheme = $match[1];
|
$scheme = !empty($match[2]) ? $match[2] : 'https';
|
||||||
$originUrl = $match[2];
|
$originUrl = !empty($match[3]) ? $match[3] : $match[4];
|
||||||
|
|
||||||
if (!in_array($originUrl, (array) $config->get('gitlab-domains'))) {
|
if (!in_array($originUrl, (array) $config->get('gitlab-domains'))) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -80,14 +80,13 @@ class Git
|
||||||
$this->throwException('Failed to clone ' . self::sanitizeUrl($url) .' via '.implode(', ', $protocols).' protocols, aborting.' . "\n\n" . implode("\n", $messages), $url);
|
$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);
|
$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);
|
$command = call_user_func($commandCallable, $url);
|
||||||
|
|
||||||
if ($bypassSshForGitHub || $bypassSshForGitLab || 0 !== $this->process->execute($command, $ignoredOutput, $cwd)) {
|
if ($bypassSshForGitHub || 0 !== $this->process->execute($command, $ignoredOutput, $cwd)) {
|
||||||
// private github/gitlab repository without git access, try https with auth
|
// private github repository without git access, try https with auth
|
||||||
if (preg_match('{^git@'.self::getGitHubDomainsRegex($this->config).':(.+?)\.git$}i', $url, $match)) {
|
if (preg_match('{^git@'.self::getGitHubDomainsRegex($this->config).':(.+?)\.git$}i', $url, $match)) {
|
||||||
|
|
||||||
if (!$this->io->hasAuthentication($match[1])) {
|
if (!$this->io->hasAuthentication($match[1])) {
|
||||||
|
@ -107,24 +106,6 @@ class Git
|
||||||
return;
|
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
|
} elseif ($this->isAuthenticationFailure($url, $match)) { // private non-github repo that failed to authenticate
|
||||||
if (strpos($match[2], '@')) {
|
if (strpos($match[2], '@')) {
|
||||||
list($authParts, $match[2]) = explode('@', $match[2], 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'))).')';
|
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)
|
public static function sanitizeUrl($message)
|
||||||
{
|
{
|
||||||
return preg_replace('{://([^@]+?):.+?@}', '://$1:***@', $message);
|
return preg_replace('{://([^@]+?):.+?@}', '://$1:***@', $message);
|
||||||
|
|
|
@ -77,7 +77,7 @@ class GitLab
|
||||||
*
|
*
|
||||||
* @return bool true on success
|
* @return bool true on success
|
||||||
*/
|
*/
|
||||||
public function authorizeOAuthInteractively($originUrl, $message = null)
|
public function authorizeOAuthInteractively($scheme, $originUrl, $message = null)
|
||||||
{
|
{
|
||||||
if ($message) {
|
if ($message) {
|
||||||
$this->io->writeError($message);
|
$this->io->writeError($message);
|
||||||
|
@ -90,7 +90,7 @@ class GitLab
|
||||||
|
|
||||||
while ($attemptCounter++ < 5) {
|
while ($attemptCounter++ < 5) {
|
||||||
try {
|
try {
|
||||||
$response = $this->createToken($originUrl);
|
$response = $this->createToken($scheme, $originUrl);
|
||||||
} catch (TransportException $e) {
|
} catch (TransportException $e) {
|
||||||
// 401 is bad credentials,
|
// 401 is bad credentials,
|
||||||
// 403 is max login attempts exceeded
|
// 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('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>"');
|
$this->io->writeError('Add it using "composer config gitlab-oauth.'.$originUrl.' <token>"');
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
|
@ -121,7 +121,7 @@ class GitLab
|
||||||
throw new \RuntimeException('Invalid GitLab credentials 5 times in a row, aborting.');
|
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: ');
|
$username = $this->io->ask('Username: ');
|
||||||
$password = $this->io->askAndHideAnswer('Password: ');
|
$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');
|
$this->io->writeError('Token successfully created');
|
||||||
|
|
||||||
|
|
|
@ -124,6 +124,7 @@ class RemoteFilesystem
|
||||||
$originUrl = 'github.com';
|
$originUrl = 'github.com';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->scheme = parse_url($fileUrl, PHP_URL_SCHEME);
|
||||||
$this->bytesMax = 0;
|
$this->bytesMax = 0;
|
||||||
$this->originUrl = $originUrl;
|
$this->originUrl = $originUrl;
|
||||||
$this->fileUrl = $fileUrl;
|
$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');
|
$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);
|
$gitLabUtil = new GitLab($this->io, $this->config, null);
|
||||||
if (!$gitLabUtil->authorizeOAuth($this->originUrl)
|
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);
|
throw new TransportException('Could not authenticate against '.$this->originUrl, 401);
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,11 +36,20 @@ class GitLabDriverTest extends \PHPUnit_Framework_TestCase
|
||||||
$this->remoteFilesystem = $this->prophesize('Composer\Util\RemoteFilesystem');
|
$this->remoteFilesystem = $this->prophesize('Composer\Util\RemoteFilesystem');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testInitialize()
|
public function getInitializeUrls()
|
||||||
{
|
{
|
||||||
$url = 'https://gitlab.com/mygroup/myproject';
|
return array(
|
||||||
$apiUrl = 'https://gitlab.com/api/v3/projects/mygroup%2Fmyproject';
|
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
|
// @link http://doc.gitlab.com/ce/api/projects.html#get-single-project
|
||||||
$projectData = <<<JSON
|
$projectData = <<<JSON
|
||||||
{
|
{
|
||||||
|
@ -74,11 +83,10 @@ JSON;
|
||||||
return $driver;
|
return $driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function testGetDist()
|
||||||
* @depends testInitialize
|
|
||||||
*/
|
|
||||||
public function testGetDist(GitLabDriver $driver)
|
|
||||||
{
|
{
|
||||||
|
$driver = $this->testInitialize('https://gitlab.com/mygroup/myproject', 'https://gitlab.com/api/v3/projects/mygroup%2Fmyproject');
|
||||||
|
|
||||||
$reference = 'c3ebdbf9cceddb82cd2089aaef8c7b992e536363';
|
$reference = 'c3ebdbf9cceddb82cd2089aaef8c7b992e536363';
|
||||||
$expected = array(
|
$expected = array(
|
||||||
'type' => 'zip',
|
'type' => 'zip',
|
||||||
|
@ -90,11 +98,10 @@ JSON;
|
||||||
$this->assertEquals($expected, $driver->getDist($reference));
|
$this->assertEquals($expected, $driver->getDist($reference));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function testGetSource()
|
||||||
* @depends testInitialize
|
|
||||||
*/
|
|
||||||
public function testGetSource(GitLabDriver $driver)
|
|
||||||
{
|
{
|
||||||
|
$driver = $this->testInitialize('https://gitlab.com/mygroup/myproject', 'https://gitlab.com/api/v3/projects/mygroup%2Fmyproject');
|
||||||
|
|
||||||
$reference = 'c3ebdbf9cceddb82cd2089aaef8c7b992e536363';
|
$reference = 'c3ebdbf9cceddb82cd2089aaef8c7b992e536363';
|
||||||
$expected = array(
|
$expected = array(
|
||||||
'type' => 'git',
|
'type' => 'git',
|
||||||
|
@ -105,11 +112,10 @@ JSON;
|
||||||
$this->assertEquals($expected, $driver->getSource($reference));
|
$this->assertEquals($expected, $driver->getSource($reference));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function testGetTags()
|
||||||
* @depends testInitialize
|
|
||||||
*/
|
|
||||||
public function testGetTags(GitLabDriver $driver)
|
|
||||||
{
|
{
|
||||||
|
$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';
|
$apiUrl = 'https://gitlab.com/api/v3/projects/mygroup%2Fmyproject/repository/tags';
|
||||||
|
|
||||||
// @link http://doc.gitlab.com/ce/api/repositories.html#list-project-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');
|
$this->assertEquals($expected, $driver->getTags(), 'Tags are cached');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function testGetBranches()
|
||||||
* @depends testInitialize
|
|
||||||
*/
|
|
||||||
public function testGetBranches(GitLabDriver $driver)
|
|
||||||
{
|
{
|
||||||
|
$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';
|
$apiUrl = 'https://gitlab.com/api/v3/projects/mygroup%2Fmyproject/repository/branches';
|
||||||
|
|
||||||
// @link http://doc.gitlab.com/ce/api/repositories.html#list-project-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.git', true),
|
||||||
array('http://gitlab.com/foo/bar.baz.git', true),
|
array('http://gitlab.com/foo/bar.baz.git', true),
|
||||||
array('https://gitlab.com/foo/bar', extension_loaded('openssl')), // Platform requirement
|
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),
|
array('http://example.com/foo/bar', false),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@ class GitLabTest extends \PHPUnit_Framework_TestCase
|
||||||
|
|
||||||
$gitLab = new GitLab($io, $config, null, $rfs);
|
$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 = new GitLab($io, $config, null, $rfs);
|
||||||
|
|
||||||
$gitLab->authorizeOAuthInteractively($this->origin);
|
$gitLab->authorizeOAuthInteractively('https', $this->origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getIOMock()
|
private function getIOMock()
|
||||||
|
|
Loading…
Reference in New Issue