Merge remote-tracking branch 'stefangr/all_bitbucket_communication_through_oauth'

main
Jordi Boggiano 8 years ago
commit dbc7629bb2

@ -14,14 +14,19 @@ namespace Composer\Repository\Vcs;
use Composer\Cache;
use Composer\Config;
use Composer\Downloader\TransportException;
use Composer\Json\JsonFile;
use Composer\IO\IOInterface;
use Composer\Util\Bitbucket;
/**
* @author Per Bernhardt <plb@webfactory.de>
*/
class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
{
/**
* @var Cache
*/
protected $cache;
protected $owner;
protected $repository;
@ -29,6 +34,11 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
protected $branches;
protected $rootIdentifier;
protected $infoCache = array();
private $hasIssues;
/**
* @var GitDriver
*/
private $gitDriver;
/**
* {@inheritDoc}
@ -47,9 +57,14 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
*/
public function getRootIdentifier()
{
if ($this->gitDriver) {
return $this->gitDriver->getRootIdentifier();
}
if (null === $this->rootIdentifier) {
$resource = $this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository;
$repoData = JsonFile::parseJson($this->getContents($resource), $resource);
$repoData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource, true), $resource);
$this->hasIssues = !empty($repoData['has_issues']);
$this->rootIdentifier = !empty($repoData['main_branch']) ? $repoData['main_branch'] : 'master';
}
@ -61,7 +76,11 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
*/
public function getUrl()
{
return $this->url;
if ($this->gitDriver) {
return $this->gitDriver->getUrl();
}
return 'https://' . $this->originUrl . '/'.$this->owner.'/'.$this->repository.'.git';
}
/**
@ -69,6 +88,10 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
*/
public function getSource($identifier)
{
if ($this->gitDriver) {
return $this->gitDriver->getSource($identifier);
}
return array('type' => 'git', 'url' => $this->getUrl(), 'reference' => $identifier);
}
@ -87,24 +110,53 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
*/
public function getComposerInformation($identifier)
{
if ($this->gitDriver) {
return $this->gitDriver->getComposerInformation($identifier);
}
if (preg_match('{[a-f0-9]{40}}i', $identifier) && $res = $this->cache->read($identifier)) {
$this->infoCache[$identifier] = JsonFile::parseJson($res);
}
if (!isset($this->infoCache[$identifier])) {
$resource = $this->getScheme() . '://bitbucket.org/'.$this->owner.'/'.$this->repository.'/raw/'.$identifier.'/composer.json';
$composer = $this->getContents($resource);
if (!$composer) {
return;
$resource = $this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/src/'.$identifier.'/composer.json';
$file = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource);
if (!is_array($file) || ! array_key_exists('data', $file)) {
return array();
}
$composer = JsonFile::parseJson($composer, $resource);
$composer = JsonFile::parseJson($file['data'], $resource);
if (empty($composer['time'])) {
$resource = $this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/changesets/'.$identifier;
$changeset = JsonFile::parseJson($this->getContents($resource), $resource);
$changeset = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource);
$composer['time'] = $changeset['timestamp'];
}
if (!isset($composer['support']['source'])) {
$label = array_search($identifier, $this->getTags()) ?: array_search($identifier, $this->getBranches()) ?: $identifier;
if (array_key_exists($label, $tags = $this->getTags())) {
$hash = $tags[$label];
} elseif (array_key_exists($label, $branches = $this->getBranches())) {
$hash = $branches[$label];
}
if (! isset($hash)) {
$composer['support']['source'] = sprintf('https://%s/%s/%s/src', $this->originUrl, $this->owner, $this->repository);
} else {
$composer['support']['source'] = sprintf(
'https://%s/%s/%s/src/%s/?at=%s',
$this->originUrl,
$this->owner,
$this->repository,
$hash,
$label
);
}
}
if (!isset($composer['support']['issues']) && $this->hasIssues) {
$composer['support']['issues'] = sprintf('https://%s/%s/%s/issues', $this->originUrl, $this->owner, $this->repository);
}
if (preg_match('{[a-f0-9]{40}}i', $identifier)) {
$this->cache->write($identifier, json_encode($composer));
@ -121,9 +173,13 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
*/
public function getTags()
{
if ($this->gitDriver) {
return $this->gitDriver->getTags();
}
if (null === $this->tags) {
$resource = $this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags';
$tagsData = JsonFile::parseJson($this->getContents($resource), $resource);
$tagsData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource);
$this->tags = array();
foreach ($tagsData as $tag => $data) {
$this->tags[$tag] = $data['raw_node'];
@ -138,9 +194,13 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
*/
public function getBranches()
{
if ($this->gitDriver) {
return $this->gitDriver->getBranches();
}
if (null === $this->branches) {
$resource = $this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/branches';
$branchData = JsonFile::parseJson($this->getContents($resource), $resource);
$branchData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource);
$this->branches = array();
foreach ($branchData as $branch => $data) {
$this->branches[$branch] = $data['raw_node'];
@ -167,4 +227,76 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
return true;
}
protected function attemptCloneFallback()
{
try {
$this->setupGitDriver($this->generateSshUrl());
return;
} catch (\RuntimeException $e) {
$this->gitDriver = null;
$this->io->writeError('<error>Failed to clone the '.$this->generateSshUrl().' repository, try running in interactive mode so that you can enter your Bitbucket OAuth consumer credentials</error>');
throw $e;
}
}
/**
* Generate an SSH URL
*
* @return string
*/
private function generateSshUrl()
{
return 'git@' . $this->originUrl . ':' . $this->owner.'/'.$this->repository.'.git';
}
/**
* Get the remote content.
*
* @param string $url The URL of content
* @param bool $fetchingRepoData
*
* @return mixed The result
*/
protected function getContentsWithOAuthCredentials($url, $fetchingRepoData = false)
{
try {
return parent::getContents($url);
} catch (TransportException $e) {
$bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process, $this->remoteFilesystem);
switch ($e->getCode()) {
case 403:
if (!$this->io->hasAuthentication($this->originUrl) && $bitbucketUtil->authorizeOAuth($this->originUrl)) {
return parent::getContents($url);
}
if (!$this->io->isInteractive() && $fetchingRepoData) {
return $this->attemptCloneFallback();
}
throw $e;
default:
throw $e;
}
}
}
/**
* @param string $url
*/
private function setupGitDriver($url)
{
$this->gitDriver = new GitDriver(
array('url' => $url),
$this->io,
$this->config,
$this->process,
$this->remoteFilesystem
);
$this->gitDriver->initialize();
}
}

@ -28,6 +28,8 @@ class Bitbucket
private $remoteFilesystem;
private $token = array();
const OAUTH2_ACCESS_TOKEN_URL = 'https://bitbucket.org/site/oauth2/access_token';
/**
* Constructor.
*
@ -81,9 +83,7 @@ class Bitbucket
private function requestAccessToken($originUrl)
{
try {
$apiUrl = 'https://bitbucket.org/site/oauth2/access_token';
$json = $this->remoteFilesystem->getContents($originUrl, $apiUrl, false, array(
$json = $this->remoteFilesystem->getContents($originUrl, self::OAUTH2_ACCESS_TOKEN_URL, false, array(
'retry-auth-failure' => false,
'http' => array(
'method' => 'POST',
@ -93,8 +93,15 @@ class Bitbucket
$this->token = json_decode($json, true);
} catch (TransportException $e) {
if (in_array($e->getCode(), array(403, 401))) {
$this->io->writeError('<error>Invalid consumer provided.</error>');
if ($e->getCode() === 400) {
$this->io->writeError('<error>Invalid OAuth consumer provided.</error>');
$this->io->writeError('This can have two reasons:');
$this->io->writeError('1. You are authenticating with a bitbucket username/password combination');
$this->io->writeError('2. You are using an OAuth consumer, but didn\'t configure a (dummy) callback url');
return false;
} elseif (in_array($e->getCode(), array(403, 401))) {
$this->io->writeError('<error>Invalid OAuth consumer provided.</error>');
$this->io->writeError('You can also add it manually later by using "composer config bitbucket-oauth.bitbucket.org <consumer-key> <consumer-secret>"');
return false;

@ -129,7 +129,9 @@ class Git
//We already have an access_token from a previous request.
if ($auth['username'] !== 'x-token-auth') {
$token = $bitbucketUtil->requestToken($match[1], $auth['username'], $auth['password']);
$this->io->setAuthentication($match[1], 'x-token-auth', $token['access_token']);
if (! empty($token)) {
$this->io->setAuthentication($match[1], 'x-token-auth', $token['access_token']);
}
}
}

@ -45,11 +45,11 @@ class ProcessExecutor
{
if ($this->io && $this->io->isDebug()) {
$safeCommand = preg_replace_callback('{(://)(?P<user>[^:/\s]+):(?P<password>[^@\s/]+)}i', function ($m) {
if (preg_match('{^[a-f0-9]{12,}$}', $m[1])) {
if (preg_match('{^[a-f0-9]{12,}$}', $m[2])) {
return '://***:***';
}
return '://'.$m[1].':***';
return '://'.$m[2].':***';
}, $command);
$this->io->writeError('Executing command ('.($cwd ?: 'CWD').'): '.$safeCommand);
}

@ -245,6 +245,11 @@ class RemoteFilesystem
unset($options['gitlab-token']);
}
if (isset($options['bitbucket-token'])) {
$fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token='.$options['bitbucket-token'];
unset($options['bitbucket-token']);
}
if (isset($options['http'])) {
$options['http']['ignore_errors'] = true;
}
@ -569,6 +574,25 @@ 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 {
$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']);
} else {
throw new TransportException('Could not authenticate against ' . $this->originUrl, 401);
}
}
} else {
// 404s are only handled for github
if ($httpStatus === 404) {
@ -671,6 +695,10 @@ class RemoteFilesystem
if ($auth['password'] === 'oauth2') {
$headers[] = 'Authorization: Bearer '.$auth['username'];
}
} elseif ('bitbucket.org' === $originUrl
&& $this->fileUrl !== Bitbucket::OAUTH2_ACCESS_TOKEN_URL && 'x-token-auth' === $auth['username']
) {
$options['bitbucket-token'] = $auth['password'];
} else {
$authStr = base64_encode($auth['username'] . ':' . $auth['password']);
$headers[] = 'Authorization: Basic '.$authStr;

@ -0,0 +1,228 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Repository\Vcs;
use Composer\Config;
use Composer\TestCase;
use Composer\Util\Filesystem;
/**
* @group bitbucket
*/
class GitBitbucketDriverTest extends TestCase
{
/** @type \Composer\IO\IOInterface|\PHPUnit_Framework_MockObject_MockObject */
private $io;
/** @type \Composer\Config */
private $config;
/** @type \Composer\Util\RemoteFilesystem|\PHPUnit_Framework_MockObject_MockObject */
private $rfs;
/** @type string */
private $home;
/** @type string */
private $originUrl = 'bitbucket.org';
protected function setUp()
{
$this->io = $this->getMock('Composer\IO\IOInterface');
$this->home = $this->getUniqueTmpDirectory();
$this->config = new Config();
$this->config->merge(array(
'config' => array(
'home' => $this->home,
),
));
$this->rfs = $this->getMockBuilder('Composer\Util\RemoteFilesystem')
->disableOriginalConstructor()
->getMock();
}
public function tearDown()
{
$fs = new Filesystem;
$fs->removeDirectory($this->home);
}
/**
* @param array $repoConfig
* @return GitBitbucketDriver
*/
private function getDriver(array $repoConfig)
{
$driver = new GitBitbucketDriver(
$repoConfig,
$this->io,
$this->config,
null,
$this->rfs
);
$driver->initialize();
return $driver;
}
public function testGetRootIdentifier()
{
$driver = $this->getDriver(array('url' => 'https://bitbucket.org/user/repo.git'));
$this->rfs->expects($this->any())
->method('getContents')
->with(
$this->originUrl,
'https://api.bitbucket.org/1.0/repositories/user/repo',
false
)
->willReturn(
'{"scm": "git", "has_wiki": false, "last_updated": "2016-05-17T13:20:21.993", "no_forks": true, "forks_count": 0, "created_on": "2015-02-18T16:22:24.688", "owner": "user", "logo": "https://bitbucket.org/user/repo/avatar/32/?ts=1463484021", "email_mailinglist": "", "is_mq": false, "size": 9975494, "read_only": false, "fork_of": null, "mq_of": null, "followers_count": 0, "state": "available", "utc_created_on": "2015-02-18 15:22:24+00:00", "website": "", "description": "", "has_issues": false, "is_fork": false, "slug": "repo", "is_private": true, "name": "repo", "language": "php", "utc_last_updated": "2016-05-17 11:20:21+00:00", "no_public_forks": true, "creator": null, "resource_uri": "/1.0/repositories/user/repo"}'
);
$this->assertEquals(
'master',
$driver->getRootIdentifier()
);
}
public function testGetParams()
{
$url = 'https://bitbucket.org/user/repo.git';
$driver = $this->getDriver(array('url' => $url));
$this->assertEquals($url, $driver->getUrl());
$this->assertEquals(
array(
'type' => 'zip',
'url' => 'https://bitbucket.org/user/repo/get/reference.zip',
'reference' => 'reference',
'shasum' => ''
),
$driver->getDist('reference')
);
$this->assertEquals(
array('type' => 'git', 'url' => $url, 'reference' => 'reference'),
$driver->getSource('reference')
);
}
public function testGetComposerInformation()
{
$driver = $this->getDriver(array('url' => 'https://bitbucket.org/user/repo.git'));
$this->rfs->expects($this->any())
->method('getContents')
->withConsecutive(
array('bitbucket.org', 'https://api.bitbucket.org/1.0/repositories/user/repo/src/master/composer.json', false),
array('bitbucket.org', 'https://api.bitbucket.org/1.0/repositories/user/repo/changesets/master', false),
array('bitbucket.org', 'https://api.bitbucket.org/1.0/repositories/user/repo/tags', false),
array('bitbucket.org', 'https://api.bitbucket.org/1.0/repositories/user/repo/branches', false)
)
->willReturnOnConsecutiveCalls(
'{"node": "937992d19d72", "path": "composer.json", "data": "{\n \"name\": \"user/repo\",\n \"description\": \"test repo\",\n \"license\": \"GPL\",\n \"authors\": [\n {\n \"name\": \"Name\",\n \"email\": \"local@domain.tld\"\n }\n ],\n \"require\": {\n \"creator/package\": \"^1.0\"\n },\n \"require-dev\": {\n \"phpunit/phpunit\": \"~4.8\"\n }\n}\n", "size": 269}',
'{"node": "937992d19d72", "files": [{"type": "modified", "file": "path/to/file"}], "raw_author": "User <local@domain.tld>", "utctimestamp": "2016-05-17 11:19:52+00:00", "author": "user", "timestamp": "2016-05-17 13:19:52", "raw_node": "937992d19d72b5116c3e8c4a04f960e5fa270b22", "parents": ["71e195a33361"], "branch": "master", "message": "Commit message\n", "revision": null, "size": -1}',
'{}',
'{"master": {"node": "937992d19d72", "files": [{"type": "modified", "file": "path/to/file"}], "raw_author": "User <local@domain.tld>", "utctimestamp": "2016-05-17 11:19:52+00:00", "author": "user", "timestamp": "2016-05-17 13:19:52", "raw_node": "937992d19d72b5116c3e8c4a04f960e5fa270b22", "parents": ["71e195a33361"], "branch": "master", "message": "Commit message\n", "revision": null, "size": -1}}'
);
$this->assertEquals(
array(
'name' => 'user/repo',
'description' => 'test repo',
'license' => 'GPL',
'authors' => array(
array(
'name' => 'Name',
'email' => 'local@domain.tld'
)
),
'require' => array(
'creator/package' => '^1.0'
),
'require-dev' => array(
'phpunit/phpunit' => '~4.8'
),
'time' => '2016-05-17 13:19:52',
'support' => array(
'source' => 'https://bitbucket.org/user/repo/src/937992d19d72b5116c3e8c4a04f960e5fa270b22/?at=master'
)
),
$driver->getComposerInformation('master')
);
}
public function testGetTags()
{
$driver = $this->getDriver(array('url' => 'https://bitbucket.org/user/repo.git'));
$this->rfs->expects($this->once())
->method('getContents')
->with(
'bitbucket.org',
'https://api.bitbucket.org/1.0/repositories/user/repo/tags',
false
)
->willReturn(
'{"1.0.1": {"node": "9b78a3932143", "files": [{"type": "modified", "file": "path/to/file"}], "branches": [], "raw_author": "User <local@domain.tld>", "utctimestamp": "2015-04-16 14:50:40+00:00", "author": "user", "timestamp": "2015-04-16 16:50:40", "raw_node": "9b78a3932143497c519e49b8241083838c8ff8a1", "parents": ["84531c04dbfc", "50c2a4635ad0"], "branch": null, "message": "Commit message\n", "revision": null, "size": -1}, "1.0.0": {"node": "d3393d514318", "files": [{"type": "modified", "file": "path/to/file2"}], "branches": [], "raw_author": "User <local@domain.tld>", "utctimestamp": "2015-04-16 09:31:45+00:00", "author": "user", "timestamp": "2015-04-16 11:31:45", "raw_node": "d3393d514318a9267d2f8ebbf463a9aaa389f8eb", "parents": ["5a29a73cd1a0"], "branch": null, "message": "Commit message\n", "revision": null, "size": -1}}'
);
$this->assertEquals(
array(
'1.0.1' => '9b78a3932143497c519e49b8241083838c8ff8a1',
'1.0.0' => 'd3393d514318a9267d2f8ebbf463a9aaa389f8eb'
),
$driver->getTags()
);
}
public function testGetBranches()
{
$driver = $this->getDriver(array('url' => 'https://bitbucket.org/user/repo.git'));
$this->rfs->expects($this->once())
->method('getContents')
->with(
'bitbucket.org',
'https://api.bitbucket.org/1.0/repositories/user/repo/branches',
false
)
->willReturn(
'{"master": {"node": "937992d19d72", "files": [{"type": "modified", "file": "path/to/file"}], "raw_author": "User <local@domain.tld>", "utctimestamp": "2016-05-17 11:19:52+00:00", "author": "user", "timestamp": "2016-05-17 13:19:52", "raw_node": "937992d19d72b5116c3e8c4a04f960e5fa270b22", "parents": ["71e195a33361"], "branch": "master", "message": "Commit message\n", "revision": null, "size": -1}}'
);
$this->assertEquals(
array(
'master' => '937992d19d72b5116c3e8c4a04f960e5fa270b22'
),
$driver->getBranches()
);
}
public function testSupports()
{
$this->assertTrue(
GitBitbucketDriver::supports($this->io, $this->config, 'https://bitbucket.org/user/repo.git')
);
$this->assertFalse(
GitBitbucketDriver::supports($this->io, $this->config, 'git@bitbucket.org:user/repo.git')
);
$this->assertFalse(
GitBitbucketDriver::supports($this->io, $this->config, 'https://github.com/user/repo.git')
);
}
}

@ -21,30 +21,138 @@ class BitbucketTest extends \PHPUnit_Framework_TestCase
{
private $username = 'username';
private $password = 'password';
private $authcode = 'authcode';
private $consumer_key = 'consumer_key';
private $consumer_secret = 'consumer_secret';
private $message = 'mymessage';
private $origin = 'bitbucket.org';
private $token = 'bitbuckettoken';
/** @type \Composer\IO\ConsoleIO|\PHPUnit_Framework_MockObject_MockObject */
private $io;
/** @type \Composer\Util\RemoteFilesystem|\PHPUnit_Framework_MockObject_MockObject */
private $rfs;
/** @type \Composer\Config|\PHPUnit_Framework_MockObject_MockObject */
private $config;
/** @type Bitbucket */
private $bitbucket;
protected function setUp()
{
$this->io = $this
->getMockBuilder('Composer\IO\ConsoleIO')
->disableOriginalConstructor()
->getMock()
;
$this->rfs = $this
->getMockBuilder('Composer\Util\RemoteFilesystem')
->disableOriginalConstructor()
->getMock()
;
$this->config = $this->getMock('Composer\Config');
$this->bitbucket = new Bitbucket($this->io, $this->config, null, $this->rfs);
}
public function testRequestAccessTokenWithValidOAuthConsumer()
{
$this->io->expects($this->once())
->method('setAuthentication')
->with($this->origin, $this->consumer_key, $this->consumer_secret);
$this->rfs->expects($this->once())
->method('getContents')
->with(
$this->origin,
Bitbucket::OAUTH2_ACCESS_TOKEN_URL,
false,
array(
'retry-auth-failure' => false,
'http' => array(
'method' => 'POST',
'content' => 'grant_type=client_credentials',
)
)
)
->willReturn(
sprintf(
'{"access_token": "%s", "scopes": "repository", "expires_in": 3600, "refresh_token": "refreshtoken", "token_type": "bearer"}',
$this->token
)
);
$this->assertEquals(
array(
'access_token' => $this->token,
'scopes' => 'repository',
'expires_in' => 3600,
'refresh_token' => 'refreshtoken',
'token_type' => 'bearer'
),
$this->bitbucket->requestToken($this->origin, $this->consumer_key, $this->consumer_secret)
);
}
public function testRequestAccessTokenWithUsernameAndPassword()
{
$this->io->expects($this->once())
->method('setAuthentication')
->with($this->origin, $this->username, $this->password);
$this->io->expects($this->any())
->method('writeError')
->withConsecutive(
array('<error>Invalid OAuth consumer provided.</error>'),
array('This can have two reasons:'),
array('1. You are authenticating with a bitbucket username/password combination'),
array('2. You are using an OAuth consumer, but didn\'t configure a (dummy) callback url')
);
$this->rfs->expects($this->once())
->method('getContents')
->with(
$this->origin,
Bitbucket::OAUTH2_ACCESS_TOKEN_URL,
false,
array(
'retry-auth-failure' => false,
'http' => array(
'method' => 'POST',
'content' => 'grant_type=client_credentials',
)
)
)
->willThrowException(
new \Composer\Downloader\TransportException(
sprintf(
'The \'%s\' URL could not be accessed: HTTP/1.1 400 BAD REQUEST',
Bitbucket::OAUTH2_ACCESS_TOKEN_URL
),
400
)
);
$this->assertEquals(array(), $this->bitbucket->requestToken($this->origin, $this->username, $this->password));
}
public function testUsernamePasswordAuthenticationFlow()
{
$io = $this->getIOMock();
$io
$this->io
->expects($this->at(0))
->method('writeError')
->with($this->message)
;
$io->expects($this->exactly(2))
$this->io->expects($this->exactly(2))
->method('askAndHideAnswer')
->withConsecutive(
array('Consumer Key (hidden): '),
array('Consumer Secret (hidden): ')
)
->willReturnOnConsecutiveCalls($this->username, $this->password);
->willReturnOnConsecutiveCalls($this->consumer_key, $this->consumer_secret);
$rfs = $this->getRemoteFilesystemMock();
$rfs
$this->rfs
->expects($this->once())
->method('getContents')
->with(
@ -56,50 +164,18 @@ class BitbucketTest extends \PHPUnit_Framework_TestCase
->willReturn(sprintf('{}', $this->token))
;
$config = $this->getConfigMock();
$config
$this->config
->expects($this->exactly(2))
->method('getAuthConfigSource')
->willReturn($this->getAuthJsonMock())
;
$config
$this->config
->expects($this->once())
->method('getConfigSource')
->willReturn($this->getConfJsonMock())
;
$bitbucket = new Bitbucket($io, $config, null, $rfs);
$this->assertTrue($bitbucket->authorizeOAuthInteractively($this->origin, $this->message));
}
private function getIOMock()
{
$io = $this
->getMockBuilder('Composer\IO\ConsoleIO')
->disableOriginalConstructor()
->getMock()
;
return $io;
}
private function getConfigMock()
{
$config = $this->getMock('Composer\Config');
return $config;
}
private function getRemoteFilesystemMock()
{
$rfs = $this
->getMockBuilder('Composer\Util\RemoteFilesystem')
->disableOriginalConstructor()
->getMock()
;
return $rfs;
$this->assertTrue($this->bitbucket->authorizeOAuthInteractively($this->origin, $this->message));
}
private function getAuthJsonMock()

Loading…
Cancel
Save