From a4e3e1b584f33b4e0a4714157c649922c2ec3ca1 Mon Sep 17 00:00:00 2001 From: berlinger-rarents Date: Mon, 26 Sep 2016 19:47:10 +0200 Subject: [PATCH 1/7] prevent (prompt for) bitbucket auth when it redirected #5584 --- src/Composer/Util/RemoteFilesystem.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index e9d5715fa..22611f444 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -344,6 +344,7 @@ class RemoteFilesystem if ($originUrl === 'bitbucket.org' && substr($fileUrl, -4) === '.zip' && preg_match('{^text/html\b}i', $contentType) + && $statusCode != 302 ) { $result = false; if ($this->retryAuthFailure) { From 64fc8ffe3d3b99e4f104e0bcf00e7212593e99ca Mon Sep 17 00:00:00 2001 From: berlinger-rarents Date: Tue, 27 Sep 2016 11:41:16 +0200 Subject: [PATCH 2/7] prevent (prompt for) auth for bitbucket public downloads #5584 --- src/Composer/Util/RemoteFilesystem.php | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 22611f444..bfc063ecd 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -175,6 +175,27 @@ class RemoteFilesystem return $value; } + /** + * @link https://github.com/composer/composer/issues/5584 + * + * @param string $urlToBitBucketFile URL to a file at bitbucket.org. + * + * @return bool Whether the given URL is a public BitBucket download which requires no authentication. + */ + public static function urlIsPublicBitBucketDownload($urlToBitBucketFile) + { + $path = parse_url($urlToBitBucketFile, PHP_URL_PATH); + + // Path for a public download follows this pattern /{user}/{repo}/downloads/{whatever} + // {@link https://blog.bitbucket.org/2009/04/12/new-feature-downloads/} + $pathParts = explode('/', $path); + if (count($pathParts) >= 4 && $pathParts[2] != 'downloads') { + return true; + } + + return false; + } + /** * Get file content or copy action. * @@ -342,9 +363,9 @@ class RemoteFilesystem // check for bitbucket login page asking to authenticate if ($originUrl === 'bitbucket.org' + && !self::urlIsPublicBitBucketDownload($fileUrl) && substr($fileUrl, -4) === '.zip' && preg_match('{^text/html\b}i', $contentType) - && $statusCode != 302 ) { $result = false; if ($this->retryAuthFailure) { From 8845ea467a6008b6de6fbbb318f201c6a68d0d34 Mon Sep 17 00:00:00 2001 From: berlinger-rarents Date: Wed, 28 Sep 2016 19:08:24 +0200 Subject: [PATCH 3/7] try bitbucket downloads first time without auth also add tests for #5584 --- src/Composer/Util/RemoteFilesystem.php | 33 ++------ .../Test/Util/RemoteFilesystemTest.php | 78 +++++++++++++++++++ 2 files changed, 86 insertions(+), 25 deletions(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index bfc063ecd..48c7b3698 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -44,6 +44,7 @@ class RemoteFilesystem private $degradedMode = false; private $redirects; private $maxRedirects = 20; + private $bitBucketUrlsTriedWithoutAuth = array(); /** * Constructor. @@ -175,27 +176,6 @@ class RemoteFilesystem return $value; } - /** - * @link https://github.com/composer/composer/issues/5584 - * - * @param string $urlToBitBucketFile URL to a file at bitbucket.org. - * - * @return bool Whether the given URL is a public BitBucket download which requires no authentication. - */ - public static function urlIsPublicBitBucketDownload($urlToBitBucketFile) - { - $path = parse_url($urlToBitBucketFile, PHP_URL_PATH); - - // Path for a public download follows this pattern /{user}/{repo}/downloads/{whatever} - // {@link https://blog.bitbucket.org/2009/04/12/new-feature-downloads/} - $pathParts = explode('/', $path); - if (count($pathParts) >= 4 && $pathParts[2] != 'downloads') { - return true; - } - - return false; - } - /** * Get file content or copy action. * @@ -267,7 +247,12 @@ class RemoteFilesystem } if (isset($options['bitbucket-token'])) { - $fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token='.$options['bitbucket-token']; + // First time be optimistic and do not use the token for a BitBucket download. + if (isset($this->bitBucketUrlsTriedWithoutAuth[$origFileUrl]) && $this->bitBucketUrlsTriedWithoutAuth[$origFileUrl]) { + $fileUrl .= (false === strpos($fileUrl,'?') ? '?' : '&') . 'access_token=' . $options['bitbucket-token']; + } else { + $this->bitBucketUrlsTriedWithoutAuth[$origFileUrl] = true; + } unset($options['bitbucket-token']); } @@ -363,9 +348,7 @@ class RemoteFilesystem // check for bitbucket login page asking to authenticate if ($originUrl === 'bitbucket.org' - && !self::urlIsPublicBitBucketDownload($fileUrl) - && substr($fileUrl, -4) === '.zip' - && preg_match('{^text/html\b}i', $contentType) + && substr($fileUrl, 0, 37) === 'https://bitbucket.org/account/signin/' ) { $result = false; if ($this->retryAuthFailure) { diff --git a/tests/Composer/Test/Util/RemoteFilesystemTest.php b/tests/Composer/Test/Util/RemoteFilesystemTest.php index a7b91ed32..f2bb7a611 100644 --- a/tests/Composer/Test/Util/RemoteFilesystemTest.php +++ b/tests/Composer/Test/Util/RemoteFilesystemTest.php @@ -191,6 +191,84 @@ class RemoteFilesystemTest extends \PHPUnit_Framework_TestCase } } + /** + * Provides URLs to public downloads at BitBucket. + * + * @return string[][] + */ + public function provideBitbucketPublicDownloadUrls() + { + return array( + array('https://bitbucket.org/berlinger-rarents/my-public-repo-with-downloads/downloads/composer-unit-test-download-me.txt', '1234'), + ); + } + + /** + * Tests that a BitBucket public download is correctly retrieved. + * + * @param string $url + * @param string $contents + * @dataProvider provideBitbucketPublicDownloadUrls + */ + public function testBitBucketPublicDownload($url, $contents) + { + $io = $this + ->getMockBuilder('Composer\IO\ConsoleIO') + ->disableOriginalConstructor() + ->getMock(); + + $rfs = new RemoteFilesystem($io); + $hostname = parse_url($url, PHP_URL_HOST); + + $result = $rfs->getContents($hostname, $url, false); + + $this->assertEquals($contents, $result); + } + + /** + * Tests that a BitBucket public download is correctly retrieved when `bitbucket-oauth` is configured. + * + * @param string $url + * @param string $contents + * @dataProvider provideBitbucketPublicDownloadUrls + */ + public function testBitBucketPublicDownloadWithAuthConfigured($url, $contents) + { + $io = $this + ->getMockBuilder('Composer\IO\ConsoleIO') + ->disableOriginalConstructor() + ->getMock(); + + $config = $this + ->getMockBuilder('Composer\Config') + ->getMock(); + $config + ->method('get') + ->withAnyParameters() + ->willReturn(array()); + + $io + ->method('hasAuthentication') + ->with('bitbucket.org') + ->willReturn(true); + $io + ->method('getAuthentication') + ->with('bitbucket.org') + ->willReturn(array( + 'username' => 'x-token-auth', + // This token is fake, but it matches a valid token's pattern. + 'password' => '1A0yeK5Po3ZEeiiRiMWLivS0jirLdoGuaSGq9NvESFx1Fsdn493wUDXC8rz_1iKVRTl1GINHEUCsDxGh5lZ=' + )); + + + $rfs = new RemoteFilesystem($io, $config); + $hostname = parse_url($url, PHP_URL_HOST); + + $result = $rfs->getContents($hostname, $url, false); + + $this->assertEquals($contents, $result); + } + protected function callGetOptionsForUrl($io, array $args = array(), array $options = array(), $fileUrl = '') { $fs = new RemoteFilesystem($io, null, $options); From 5123c5cf76caf3e9274f649d934d69dcedde87ec Mon Sep 17 00:00:00 2001 From: berlinger-rarents Date: Thu, 29 Sep 2016 10:55:14 +0200 Subject: [PATCH 4/7] remove redundant truth check --- src/Composer/Util/RemoteFilesystem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 48c7b3698..ce2437669 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -248,7 +248,7 @@ class RemoteFilesystem if (isset($options['bitbucket-token'])) { // First time be optimistic and do not use the token for a BitBucket download. - if (isset($this->bitBucketUrlsTriedWithoutAuth[$origFileUrl]) && $this->bitBucketUrlsTriedWithoutAuth[$origFileUrl]) { + if (isset($this->bitBucketUrlsTriedWithoutAuth[$origFileUrl])) { $fileUrl .= (false === strpos($fileUrl,'?') ? '?' : '&') . 'access_token=' . $options['bitbucket-token']; } else { $this->bitBucketUrlsTriedWithoutAuth[$origFileUrl] = true; From d338a95174c5ab65b336b8361eb2db8adc412aa6 Mon Sep 17 00:00:00 2001 From: Roel Arents Date: Thu, 29 Sep 2016 17:29:09 +0200 Subject: [PATCH 5/7] use seldaek's bitbucket repo for unit tests instead of 3rd party --- tests/Composer/Test/Util/RemoteFilesystemTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Composer/Test/Util/RemoteFilesystemTest.php b/tests/Composer/Test/Util/RemoteFilesystemTest.php index f2bb7a611..b776c433d 100644 --- a/tests/Composer/Test/Util/RemoteFilesystemTest.php +++ b/tests/Composer/Test/Util/RemoteFilesystemTest.php @@ -199,7 +199,7 @@ class RemoteFilesystemTest extends \PHPUnit_Framework_TestCase public function provideBitbucketPublicDownloadUrls() { return array( - array('https://bitbucket.org/berlinger-rarents/my-public-repo-with-downloads/downloads/composer-unit-test-download-me.txt', '1234'), + array('https://bitbucket.org/seldaek/composer-live-test-repo/downloads/composer-unit-test-download-me.txt', '1234'), ); } From 489a8f3d5a110821c226762289d382af77d93bcf Mon Sep 17 00:00:00 2001 From: Roel Arents Date: Thu, 29 Sep 2016 21:16:47 +0200 Subject: [PATCH 6/7] revert to simply making an exception (no acces_token) for bitbucket/user/repo/downloads URLs [#5584] --- src/Composer/Util/RemoteFilesystem.php | 30 +++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index ce2437669..3888dbc25 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -44,7 +44,6 @@ class RemoteFilesystem private $degradedMode = false; private $redirects; private $maxRedirects = 20; - private $bitBucketUrlsTriedWithoutAuth = array(); /** * Constructor. @@ -176,6 +175,27 @@ class RemoteFilesystem return $value; } + /** + * @link https://github.com/composer/composer/issues/5584 + * + * @param string $urlToBitBucketFile URL to a file at bitbucket.org. + * + * @return bool Whether the given URL is a public BitBucket download which requires no authentication. + */ + public static function urlIsPublicBitBucketDownload($urlToBitBucketFile) + { + $path = parse_url($urlToBitBucketFile, PHP_URL_PATH); + + // Path for a public download follows this pattern /{user}/{repo}/downloads/{whatever} + // {@link https://blog.bitbucket.org/2009/04/12/new-feature-downloads/} + $pathParts = explode('/', $path); + if (count($pathParts) >= 4 && $pathParts[2] != 'downloads') { + return true; + } + + return false; + } + /** * Get file content or copy action. * @@ -248,10 +268,8 @@ class RemoteFilesystem if (isset($options['bitbucket-token'])) { // First time be optimistic and do not use the token for a BitBucket download. - if (isset($this->bitBucketUrlsTriedWithoutAuth[$origFileUrl])) { + if (!static::urlIsPublicBitBucketDownload($origFileUrl)) { $fileUrl .= (false === strpos($fileUrl,'?') ? '?' : '&') . 'access_token=' . $options['bitbucket-token']; - } else { - $this->bitBucketUrlsTriedWithoutAuth[$origFileUrl] = true; } unset($options['bitbucket-token']); } @@ -348,7 +366,9 @@ class RemoteFilesystem // check for bitbucket login page asking to authenticate if ($originUrl === 'bitbucket.org' - && substr($fileUrl, 0, 37) === 'https://bitbucket.org/account/signin/' + && !static::urlIsPublicBitBucketDownload($fileUrl) + && substr($fileUrl, -4) === '.zip' + && preg_match('{^text/html\b}i', $contentType) ) { $result = false; if ($this->retryAuthFailure) { From c0e28a90437d6a461954203d5df855edf2878cb9 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 10 Oct 2016 14:03:30 +0200 Subject: [PATCH 7/7] Remove static/public method --- src/Composer/Util/RemoteFilesystem.php | 48 +++++++++++++------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 3888dbc25..9bfb1e2dd 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -175,27 +175,6 @@ class RemoteFilesystem return $value; } - /** - * @link https://github.com/composer/composer/issues/5584 - * - * @param string $urlToBitBucketFile URL to a file at bitbucket.org. - * - * @return bool Whether the given URL is a public BitBucket download which requires no authentication. - */ - public static function urlIsPublicBitBucketDownload($urlToBitBucketFile) - { - $path = parse_url($urlToBitBucketFile, PHP_URL_PATH); - - // Path for a public download follows this pattern /{user}/{repo}/downloads/{whatever} - // {@link https://blog.bitbucket.org/2009/04/12/new-feature-downloads/} - $pathParts = explode('/', $path); - if (count($pathParts) >= 4 && $pathParts[2] != 'downloads') { - return true; - } - - return false; - } - /** * Get file content or copy action. * @@ -267,8 +246,8 @@ class RemoteFilesystem } if (isset($options['bitbucket-token'])) { - // First time be optimistic and do not use the token for a BitBucket download. - if (!static::urlIsPublicBitBucketDownload($origFileUrl)) { + // skip using the token for BitBucket downloads as these are not working with auth + if (!$this->isPublicBitBucketDownload($origFileUrl)) { $fileUrl .= (false === strpos($fileUrl,'?') ? '?' : '&') . 'access_token=' . $options['bitbucket-token']; } unset($options['bitbucket-token']); @@ -366,7 +345,7 @@ class RemoteFilesystem // check for bitbucket login page asking to authenticate if ($originUrl === 'bitbucket.org' - && !static::urlIsPublicBitBucketDownload($fileUrl) + && !$this->isPublicBitBucketDownload($fileUrl) && substr($fileUrl, -4) === '.zip' && preg_match('{^text/html\b}i', $contentType) ) { @@ -1007,4 +986,25 @@ class RemoteFilesystem return parse_url($url, PHP_URL_HOST).':'.$port; } + + /** + * @link https://github.com/composer/composer/issues/5584 + * + * @param string $urlToBitBucketFile URL to a file at bitbucket.org. + * + * @return bool Whether the given URL is a public BitBucket download which requires no authentication. + */ + private function isPublicBitBucketDownload($urlToBitBucketFile) + { + $path = parse_url($urlToBitBucketFile, PHP_URL_PATH); + + // Path for a public download follows this pattern /{user}/{repo}/downloads/{whatever} + // {@link https://blog.bitbucket.org/2009/04/12/new-feature-downloads/} + $pathParts = explode('/', $path); + if (count($pathParts) >= 4 && $pathParts[2] != 'downloads') { + return true; + } + + return false; + } }