Store access-token for re-use
Store the Bitbucket access-token (and the expiration time) so it can be re-used within the time it is valid. The Bitbucket::requestToken and Bitbucket::getToken now only return the access-token and not all other parameters it receives from the Bitbucket API.pull/6094/head
parent
810267e2a7
commit
a4af559ca8
|
@ -79,7 +79,9 @@ class Config
|
|||
private $config;
|
||||
private $baseDir;
|
||||
private $repositories;
|
||||
/** @var ConfigSourceInterface */
|
||||
private $configSource;
|
||||
/** @var ConfigSourceInterface */
|
||||
private $authConfigSource;
|
||||
private $useEnvironment;
|
||||
private $warnedHosts = array();
|
||||
|
|
|
@ -27,6 +27,7 @@ class Bitbucket
|
|||
private $process;
|
||||
private $remoteFilesystem;
|
||||
private $token = array();
|
||||
private $time;
|
||||
|
||||
const OAUTH2_ACCESS_TOKEN_URL = 'https://bitbucket.org/site/oauth2/access_token';
|
||||
|
||||
|
@ -37,21 +38,26 @@ class Bitbucket
|
|||
* @param Config $config The composer configuration
|
||||
* @param ProcessExecutor $process Process instance, injectable for mocking
|
||||
* @param RemoteFilesystem $remoteFilesystem Remote Filesystem, injectable for mocking
|
||||
* @param int $time Timestamp, injectable for mocking
|
||||
*/
|
||||
public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, RemoteFilesystem $remoteFilesystem = null)
|
||||
public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, RemoteFilesystem $remoteFilesystem = null, $time = null)
|
||||
{
|
||||
$this->io = $io;
|
||||
$this->config = $config;
|
||||
$this->process = $process ?: new ProcessExecutor;
|
||||
$this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($this->io, $config);
|
||||
$this->time = $time;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @return string
|
||||
*/
|
||||
public function getToken()
|
||||
{
|
||||
return $this->token;
|
||||
if (! isset($this->token['access_token'])) {
|
||||
return '';
|
||||
}
|
||||
return $this->token['access_token'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -109,6 +115,8 @@ class Bitbucket
|
|||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -151,16 +159,13 @@ class Bitbucket
|
|||
|
||||
$this->io->setAuthentication($originUrl, $consumerKey, $consumerSecret);
|
||||
|
||||
$this->requestAccessToken($originUrl);
|
||||
if (! $this->requestAccessToken($originUrl)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// store value in user config
|
||||
$this->config->getConfigSource()->removeConfigSetting('bitbucket-oauth.'.$originUrl);
|
||||
$this->storeInAuthConfig($originUrl, $consumerKey, $consumerSecret);
|
||||
|
||||
$consumer = array(
|
||||
"consumer-key" => $consumerKey,
|
||||
"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);
|
||||
|
||||
|
@ -175,17 +180,66 @@ class Bitbucket
|
|||
* @param string $originUrl
|
||||
* @param string $consumerKey
|
||||
* @param string $consumerSecret
|
||||
* @return array
|
||||
* @return string
|
||||
*/
|
||||
public function requestToken($originUrl, $consumerKey, $consumerSecret)
|
||||
{
|
||||
if (!empty($this->token)) {
|
||||
return $this->token;
|
||||
if (!empty($this->token) || $this->getTokenFromConfig($originUrl)) {
|
||||
return $this->token['access_token'];
|
||||
}
|
||||
|
||||
$this->io->setAuthentication($originUrl, $consumerKey, $consumerSecret);
|
||||
$this->requestAccessToken($originUrl);
|
||||
if (! $this->requestAccessToken($originUrl)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->token;
|
||||
$this->storeInAuthConfig($originUrl, $consumerKey, $consumerSecret);
|
||||
|
||||
return $this->token['access_token'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the new/updated credentials to the configuration
|
||||
* @param string $originUrl
|
||||
* @param string $consumerKey
|
||||
* @param string $consumerSecret
|
||||
*/
|
||||
private function storeInAuthConfig($originUrl, $consumerKey, $consumerSecret)
|
||||
{
|
||||
$this->config->getConfigSource()->removeConfigSetting('bitbucket-oauth.'.$originUrl);
|
||||
|
||||
$time = null === $this->time ? time() : $this->time;
|
||||
$consumer = array(
|
||||
"consumer-key" => $consumerKey,
|
||||
"consumer-secret" => $consumerSecret,
|
||||
"access-token" => $this->token['access_token'],
|
||||
"access-token-expiration" => $time + $this->token['expires_in']
|
||||
);
|
||||
|
||||
$this->config->getAuthConfigSource()->addConfigSetting('bitbucket-oauth.'.$originUrl, $consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $originUrl
|
||||
* @return bool
|
||||
*/
|
||||
private function getTokenFromConfig($originUrl)
|
||||
{
|
||||
$authConfig = $this->config->get('bitbucket-oauth');
|
||||
|
||||
if (empty($authConfig) ||
|
||||
! isset($authConfig[$originUrl]) ||
|
||||
! isset($authConfig[$originUrl]['access-token']) ||
|
||||
! isset($authConfig[$originUrl]['access-token-expiration']) ||
|
||||
time() > $authConfig[$originUrl]['access-token-expiration']
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->token = array(
|
||||
'access_token' => $authConfig[$originUrl]['access-token']
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -122,17 +122,17 @@ class Git
|
|||
|
||||
if (!$bitbucketUtil->authorizeOAuth($match[1]) && $this->io->isInteractive()) {
|
||||
$bitbucketUtil->authorizeOAuthInteractively($match[1], $message);
|
||||
$token = $bitbucketUtil->getToken();
|
||||
$this->io->setAuthentication($match[1], 'x-token-auth', $token['access_token']);
|
||||
$access_token = $bitbucketUtil->getToken();
|
||||
$this->io->setAuthentication($match[1], 'x-token-auth', $access_token);
|
||||
}
|
||||
} else { //We're authenticating with a locally stored consumer.
|
||||
$auth = $this->io->getAuthentication($match[1]);
|
||||
|
||||
//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']);
|
||||
if (!empty($token)) {
|
||||
$this->io->setAuthentication($match[1], 'x-token-auth', $token['access_token']);
|
||||
$access_token = $bitbucketUtil->requestToken($match[1], $auth['username'], $auth['password']);
|
||||
if (! empty($access_token)) {
|
||||
$this->io->setAuthentication($match[1], 'x-token-auth', $access_token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -601,9 +601,9 @@ class RemoteFilesystem
|
|||
$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']);
|
||||
if (! empty($token)) {
|
||||
$this->io->setAuthentication($this->originUrl, 'x-token-auth', $token['access_token']);
|
||||
$access_token = $bitbucketUtil->requestToken($this->originUrl, $auth['username'], $auth['password']);
|
||||
if (! empty($access_token)) {
|
||||
$this->io->setAuthentication($this->originUrl, 'x-token-auth', $access_token);
|
||||
$askForOAuthToken = false;
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -35,6 +35,8 @@ class BitbucketTest extends \PHPUnit_Framework_TestCase
|
|||
private $config;
|
||||
/** @type Bitbucket */
|
||||
private $bitbucket;
|
||||
/** @var int */
|
||||
private $time;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
|
@ -52,7 +54,9 @@ class BitbucketTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
$this->config = $this->getMock('Composer\Config');
|
||||
|
||||
$this->bitbucket = new Bitbucket($this->io, $this->config, null, $this->rfs);
|
||||
$this->time = time();
|
||||
|
||||
$this->bitbucket = new Bitbucket($this->io, $this->config, null, $this->rfs, $this->time);
|
||||
}
|
||||
|
||||
public function testRequestAccessTokenWithValidOAuthConsumer()
|
||||
|
@ -82,14 +86,15 @@ class BitbucketTest extends \PHPUnit_Framework_TestCase
|
|||
)
|
||||
);
|
||||
|
||||
$this->config->expects($this->once())
|
||||
->method('get')
|
||||
->with('bitbucket-oauth')
|
||||
->willReturn(null);
|
||||
|
||||
$this->setExpectationsForStoringAccessToken();
|
||||
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'access_token' => $this->token,
|
||||
'scopes' => 'repository',
|
||||
'expires_in' => 3600,
|
||||
'refresh_token' => 'refreshtoken',
|
||||
'token_type' => 'bearer'
|
||||
),
|
||||
$this->token,
|
||||
$this->bitbucket->requestToken($this->origin, $this->consumer_key, $this->consumer_secret)
|
||||
);
|
||||
}
|
||||
|
@ -133,7 +138,12 @@ class BitbucketTest extends \PHPUnit_Framework_TestCase
|
|||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(array(), $this->bitbucket->requestToken($this->origin, $this->username, $this->password));
|
||||
$this->config->expects($this->once())
|
||||
->method('get')
|
||||
->with('bitbucket-oauth')
|
||||
->willReturn(null);
|
||||
|
||||
$this->assertEquals('', $this->bitbucket->requestToken($this->origin, $this->username, $this->password));
|
||||
}
|
||||
|
||||
public function testUsernamePasswordAuthenticationFlow()
|
||||
|
@ -161,67 +171,51 @@ class BitbucketTest extends \PHPUnit_Framework_TestCase
|
|||
$this->isFalse(),
|
||||
$this->anything()
|
||||
)
|
||||
->willReturn(sprintf('{}', $this->token))
|
||||
;
|
||||
|
||||
$authJson = $this->getAuthJsonMock();
|
||||
$this->config
|
||||
->expects($this->exactly(3))
|
||||
->method('getAuthConfigSource')
|
||||
->willReturn($authJson)
|
||||
;
|
||||
$this->config
|
||||
->expects($this->once())
|
||||
->method('getConfigSource')
|
||||
->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
|
||||
->willReturn(
|
||||
sprintf(
|
||||
'{"access_token": "%s", "scopes": "repository", "expires_in": 3600, "refresh_token": "refresh_token", "token_type": "bearer"}',
|
||||
$this->token
|
||||
)
|
||||
);
|
||||
)
|
||||
;
|
||||
|
||||
$authJson->expects($this->once())
|
||||
->method('removeConfigSetting')
|
||||
->with('http-basic.'.$this->origin);
|
||||
$this->setExpectationsForStoringAccessToken(true);
|
||||
|
||||
$this->assertTrue($this->bitbucket->authorizeOAuthInteractively($this->origin, $this->message));
|
||||
}
|
||||
|
||||
private function getAuthJsonMock()
|
||||
private function setExpectationsForStoringAccessToken($removeBasicAuth = false)
|
||||
{
|
||||
$authjson = $this
|
||||
->getMockBuilder('Composer\Config\JsonConfigSource')
|
||||
->disableOriginalConstructor()
|
||||
->getMock()
|
||||
;
|
||||
$authjson
|
||||
->expects($this->atLeastOnce())
|
||||
->method('getName')
|
||||
->willReturn('auth.json')
|
||||
;
|
||||
$configSourceMock = $this->getMock('Composer\Config\ConfigSourceInterface');
|
||||
$this->config->expects($this->once())
|
||||
->method('getConfigSource')
|
||||
->willReturn($configSourceMock);
|
||||
|
||||
return $authjson;
|
||||
}
|
||||
|
||||
private function getConfJsonMock()
|
||||
{
|
||||
$confjson = $this
|
||||
->getMockBuilder('Composer\Config\JsonConfigSource')
|
||||
->disableOriginalConstructor()
|
||||
->getMock()
|
||||
;
|
||||
$confjson
|
||||
->expects($this->atLeastOnce())
|
||||
$configSourceMock->expects($this->once())
|
||||
->method('removeConfigSetting')
|
||||
->with('bitbucket-oauth.'.$this->origin)
|
||||
;
|
||||
->with('bitbucket-oauth.' . $this->origin);
|
||||
|
||||
return $confjson;
|
||||
$authConfigSourceMock = $this->getMock('Composer\Config\ConfigSourceInterface');
|
||||
$this->config->expects($this->atLeastOnce())
|
||||
->method('getAuthConfigSource')
|
||||
->willReturn($authConfigSourceMock);
|
||||
|
||||
$authConfigSourceMock->expects($this->once())
|
||||
->method('addConfigSetting')
|
||||
->with(
|
||||
'bitbucket-oauth.' . $this->origin,
|
||||
array(
|
||||
"consumer-key" => $this->consumer_key,
|
||||
"consumer-secret" => $this->consumer_secret,
|
||||
"access-token" => $this->token,
|
||||
"access-token-expiration" => $this->time + 3600
|
||||
)
|
||||
);
|
||||
|
||||
if ($removeBasicAuth) {
|
||||
$authConfigSourceMock->expects($this->once())
|
||||
->method('removeConfigSetting')
|
||||
->with('http-basic.' . $this->origin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue