Browse Source

Merge remote-tracking branch 'stefangr/fix_minor_bitbucket_oauth_issues'

Jordi Boggiano 8 years ago
parent
commit
8ffe224c0d

+ 2 - 0
src/Composer/Util/Bitbucket.php

@@ -161,6 +161,8 @@ class Bitbucket
             "consumer-secret" => $consumerSecret,
         );
         $this->config->getAuthConfigSource()->addConfigSetting('bitbucket-oauth.'.$originUrl, $consumer);
+        // Remove conflicting basic auth credentials (if available)
+        $this->config->getAuthConfigSource()->removeConfigSetting('http-basic.' . $originUrl);
 
         $this->io->writeError('<info>Consumer stored successfully.</info>');
 

+ 47 - 10
src/Composer/Util/RemoteFilesystem.php

@@ -175,6 +175,24 @@ class RemoteFilesystem
         return $value;
     }
 
+    /**
+     * @param  array       $headers array of returned headers
+     * @return string|null
+     */
+    public function findContentType(array $headers)
+    {
+        $value = null;
+        foreach ($headers as $header) {
+            if (preg_match('/^Content-type:\s*([^;]+)/i', $header, $match)) {
+                // In case of redirects, http_response_headers contains the headers of all responses
+                // so we can not return directly and need to keep iterating
+                $value = $match[1];
+            }
+        }
+
+        return $value;
+    }
+
     /**
      * Get file content or copy action.
      *
@@ -334,8 +352,21 @@ class RemoteFilesystem
         }
 
         $statusCode = null;
+        $contentType = null;
         if (!empty($http_response_header[0])) {
             $statusCode = $this->findStatusCode($http_response_header);
+            $contentType = $this->findContentType($http_response_header);
+        }
+
+        if ($originUrl === 'bitbucket.org' &&
+            preg_match('/\.zip$/', $fileUrl) &&
+            $contentType === 'text/html'
+        ) {
+            // The received content is a login page asking to authenticate
+            $result = false;
+            if ($this->retryAuthFailure) {
+                $this->promptAuthAndRetry(401);
+            }
         }
 
         // handle 3xx redirects for php<5.6, 304 Not Modified is excluded
@@ -575,24 +606,30 @@ class RemoteFilesystem
                 throw new TransportException('Could not authenticate against '.$this->originUrl, 401);
             }
         } elseif ($this->config && $this->originUrl === 'bitbucket.org') {
-            if (! $this->io->hasAuthentication($this->originUrl)) {
-                $message = "\n".'Could not fetch ' . $this->fileUrl . ', please create a bitbucket OAuth token to access private repos';
-                $bitBucketUtil = new Bitbucket($this->io, $this->config);
-                if (! $bitBucketUtil->authorizeOAuth($this->originUrl)
-                    && (! $this->io->isInteractive() || !$bitBucketUtil->authorizeOAuthInteractively($this->originUrl, $message))
-                ) {
-                    throw new TransportException('Could not authenticate against ' . $this->originUrl, 401);
-                }
-            } else {
+            $askForOAuthToken = true;
+            if ($this->io->hasAuthentication($this->originUrl)) {
                 $auth = $this->io->getAuthentication($this->originUrl);
                 if ($auth['username'] !== 'x-token-auth') {
                     $bitbucketUtil = new Bitbucket($this->io, $this->config);
                     $token = $bitbucketUtil->requestToken($this->originUrl, $auth['username'], $auth['password']);
-                    $this->io->setAuthentication($this->originUrl, 'x-token-auth', $token['access_token']);
+                    if (! empty($token)) {
+                        $this->io->setAuthentication($this->originUrl, 'x-token-auth', $token['access_token']);
+                        $askForOAuthToken = false;
+                    }
                 } else {
                     throw new TransportException('Could not authenticate against ' . $this->originUrl, 401);
                 }
             }
+
+            if ($askForOAuthToken) {
+                $message = "\n".'Could not fetch ' . $this->fileUrl . ', please create a bitbucket OAuth token to ' . ($httpStatus === 401 ? 'to access private repos' : 'to go over the API rate limit');
+                $bitBucketUtil = new Bitbucket($this->io, $this->config);
+                if (! $bitBucketUtil->authorizeOAuth($this->originUrl)
+                    && (! $this->io->isInteractive() || !$bitBucketUtil->authorizeOAuthInteractively($this->originUrl, $message))
+                ) {
+                    throw new TransportException('Could not authenticate against ' . $this->originUrl, 401);
+                }
+            }
         } else {
             // 404s are only handled for github
             if ($httpStatus === 404) {

+ 17 - 2
tests/Composer/Test/Util/BitbucketTest.php

@@ -164,10 +164,11 @@ class BitbucketTest extends \PHPUnit_Framework_TestCase
             ->willReturn(sprintf('{}', $this->token))
         ;
 
+        $authJson = $this->getAuthJsonMock();
         $this->config
-            ->expects($this->exactly(2))
+            ->expects($this->exactly(3))
             ->method('getAuthConfigSource')
-            ->willReturn($this->getAuthJsonMock())
+            ->willReturn($authJson)
         ;
         $this->config
             ->expects($this->once())
@@ -175,6 +176,20 @@ class BitbucketTest extends \PHPUnit_Framework_TestCase
             ->willReturn($this->getConfJsonMock())
         ;
 
+        $authJson->expects($this->once())
+            ->method('addConfigSetting')
+            ->with(
+                'bitbucket-oauth.'.$this->origin,
+                array(
+                    'consumer-key' => $this->consumer_key,
+                    'consumer-secret' => $this->consumer_secret
+                )
+            );
+
+        $authJson->expects($this->once())
+            ->method('removeConfigSetting')
+            ->with('http-basic.'.$this->origin);
+
         $this->assertTrue($this->bitbucket->authorizeOAuthInteractively($this->origin, $this->message));
     }