From c23670c3ecb32e433beceab9df79c10d9d39f54d Mon Sep 17 00:00:00 2001 From: Michael Chekin Date: Mon, 4 May 2020 09:48:23 +0200 Subject: [PATCH] Add Util\AuthHelper unit test coverage (#8863) * Add AuthHelper::addAuthenticationHeader() test on missing authentication credentials. * Add AuthHelper::addAuthenticationHeader() test on bearer password. * Add AuthHelper::addAuthenticationHeader() test on Github token. * Add AuthHelper::addAuthenticationHeader() test on Gitlab Oauth token. * Add $authenticationDisplayMessage write expectation to AuthHelper::addAuthenticationHeader() tests. * Add AuthHelper::addAuthenticationHeader() test on Gitlab private token. * Add AuthHelper::addAuthenticationHeader() test on Bitbucket Oauth token. * Add AuthHelper::addAuthenticationHeader() test on Bitbucket public urls. * Add AuthHelper::addAuthenticationHeader() test on Basic Http Authentication. * Add AuthHelper::isPublicBitBucketDownload() tests. * Rename AuthHelperTest $credentials variable to $auth. * Add AuthHelper::storeAuth() test for auto-store option. * Add AuthHelper::storeAuth() test for user prompt and y(es) answer. * Add AuthHelper::storeAuth() test for user prompt and n(o) answer. * Add AuthHelper::storeAuth() test for user prompt with invalid answer. * Add AuthHelper::promptAuthIfNeeded() test for Github authentication failure. - add GitHub hard dependency mock (new GitHub(...) mock) * Run AuthHelper::promptAuthIfNeeded() tests only with PHP > 5.3 * Run AuthHelper::promptAuthIfNeeded() tests only with PHP >= 5.4 * Run AuthHelper::promptAuthIfNeeded() tests only with PHP 5.4 * Exclude PHPStan analyses of '../tests/Composer/Test/Util/Mocks/*' * Exclude AuthHelper::promptAuthIfNeeded() tests from current pull request. * Extract repetitive AuthHelperTest authentication expectation into a method. --- tests/Composer/Test/Util/AuthHelperTest.php | 525 ++++++++++++++++++++ 1 file changed, 525 insertions(+) create mode 100644 tests/Composer/Test/Util/AuthHelperTest.php diff --git a/tests/Composer/Test/Util/AuthHelperTest.php b/tests/Composer/Test/Util/AuthHelperTest.php new file mode 100644 index 000000000..567299345 --- /dev/null +++ b/tests/Composer/Test/Util/AuthHelperTest.php @@ -0,0 +1,525 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Util; + +use Composer\IO\IOInterface; +use Composer\Test\TestCase; +use Composer\Util\AuthHelper; +use Composer\Util\Bitbucket; +use RuntimeException; + +/** + * @author Michael Chekin + */ +class AuthHelperTest extends TestCase +{ + /** @type \Composer\IO\IOInterface|\PHPUnit_Framework_MockObject_MockObject */ + private $io; + + /** @type \Composer\Config|\PHPUnit_Framework_MockObject_MockObject */ + private $config; + + /** @type AuthHelper */ + private $authHelper; + + protected function setUp() + { + $this->io = $this + ->getMockBuilder('Composer\IO\IOInterface') + ->disableOriginalConstructor() + ->getMock(); + + $this->config = $this->getMockBuilder('Composer\Config')->getMock(); + + $this->authHelper = new AuthHelper($this->io, $this->config); + } + + public function testAddAuthenticationHeaderWithoutAuthCredentials() + { + $headers = array( + 'Accept-Encoding: gzip', + 'Connection: close' + ); + $origin = 'http://example.org'; + $url = 'file://' . __FILE__; + + $this->io->expects($this->once()) + ->method('hasAuthentication') + ->with($origin) + ->willReturn(false); + + $this->assertSame( + $headers, + $this->authHelper->addAuthenticationHeader($headers, $origin, $url) + ); + } + + public function testAddAuthenticationHeaderWithBearerPassword() + { + $headers = array( + 'Accept-Encoding: gzip', + 'Connection: close' + ); + $origin = 'http://example.org'; + $url = 'file://' . __FILE__; + $auth = array( + 'username' => 'my_username', + 'password' => 'bearer' + ); + + $this->expectsAuthentication($origin, $auth); + + $expectedHeaders = array_merge($headers, array('Authorization: Bearer ' . $auth['username'])); + + $this->assertSame( + $expectedHeaders, + $this->authHelper->addAuthenticationHeader($headers, $origin, $url) + ); + } + + public function testAddAuthenticationHeaderWithGithubToken() + { + $headers = array( + 'Accept-Encoding: gzip', + 'Connection: close' + ); + $origin = 'github.com'; + $url = 'https://api.github.com/'; + $auth = array( + 'username' => 'my_username', + 'password' => 'x-oauth-basic' + ); + + $this->expectsAuthentication($origin, $auth); + + $this->io->expects($this->once()) + ->method('writeError') + ->with('Using GitHub token authentication', true, IOInterface::DEBUG); + + $expectedHeaders = array_merge($headers, array('Authorization: token ' . $auth['username'])); + + $this->assertSame( + $expectedHeaders, + $this->authHelper->addAuthenticationHeader($headers, $origin, $url) + ); + } + + public function testAddAuthenticationHeaderWithGitlabOathToken() + { + $headers = array( + 'Accept-Encoding: gzip', + 'Connection: close' + ); + $origin = 'gitlab.com'; + $url = 'https://api.gitlab.com/'; + $auth = array( + 'username' => 'my_username', + 'password' => 'oauth2' + ); + + $this->expectsAuthentication($origin, $auth); + + $this->config->expects($this->once()) + ->method('get') + ->with('gitlab-domains') + ->willReturn(array($origin)); + + $this->io->expects($this->once()) + ->method('writeError') + ->with('Using GitLab OAuth token authentication', true, IOInterface::DEBUG); + + $expectedHeaders = array_merge($headers, array('Authorization: Bearer ' . $auth['username'])); + + $this->assertSame( + $expectedHeaders, + $this->authHelper->addAuthenticationHeader($headers, $origin, $url) + ); + } + + public function gitlabPrivateTokenProvider() + { + return array( + array('private-token'), + array('gitlab-ci-token'), + ); + } + + /** + * @dataProvider gitlabPrivateTokenProvider + * + * @param string $password + */ + public function testAddAuthenticationHeaderWithGitlabPrivateToken($password) + { + $headers = array( + 'Accept-Encoding: gzip', + 'Connection: close' + ); + $origin = 'gitlab.com'; + $url = 'https://api.gitlab.com/'; + $auth = array( + 'username' => 'my_username', + 'password' => $password + ); + + $this->expectsAuthentication($origin, $auth); + + $this->config->expects($this->once()) + ->method('get') + ->with('gitlab-domains') + ->willReturn(array($origin)); + + $this->io->expects($this->once()) + ->method('writeError') + ->with('Using GitLab private token authentication', true, IOInterface::DEBUG); + + $expectedHeaders = array_merge($headers, array('PRIVATE-TOKEN: ' . $auth['username'])); + + $this->assertSame( + $expectedHeaders, + $this->authHelper->addAuthenticationHeader($headers, $origin, $url) + ); + } + + public function testAddAuthenticationHeaderWithBitbucketOathToken() + { + $headers = array( + 'Accept-Encoding: gzip', + 'Connection: close' + ); + $origin = 'bitbucket.org'; + $url = 'https://bitbucket.org/site/oauth2/authorize'; + $auth = array( + 'username' => 'x-token-auth', + 'password' => 'my_password' + ); + + $this->expectsAuthentication($origin, $auth); + + $this->config->expects($this->once()) + ->method('get') + ->with('gitlab-domains') + ->willReturn(array()); + + $this->io->expects($this->once()) + ->method('writeError') + ->with('Using Bitbucket OAuth token authentication', true, IOInterface::DEBUG); + + $expectedHeaders = array_merge($headers, array('Authorization: Bearer ' . $auth['password'])); + + $this->assertSame( + $expectedHeaders, + $this->authHelper->addAuthenticationHeader($headers, $origin, $url) + ); + } + + public function bitbucketPublicUrlProvider() + { + return array( + array('https://bitbucket.org/user/repo/downloads/whatever'), + array('https://bbuseruploads.s3.amazonaws.com/9421ee72-638e-43a9-82ea-39cfaae2bfaa/downloads/b87c59d9-54f3-4922-b711-d89059ec3bcf'), + ); + } + + /** + * @dataProvider bitbucketPublicUrlProvider + * + * @param string $url + */ + public function testAddAuthenticationHeaderWithBitbucketPublicUrl($url) + { + $headers = array( + 'Accept-Encoding: gzip', + 'Connection: close' + ); + $origin = 'bitbucket.org'; + $auth = array( + 'username' => 'x-token-auth', + 'password' => 'my_password' + ); + + $this->expectsAuthentication($origin, $auth); + + $this->config->expects($this->once()) + ->method('get') + ->with('gitlab-domains') + ->willReturn(array()); + + $this->assertSame( + $headers, + $this->authHelper->addAuthenticationHeader($headers, $origin, $url) + ); + } + + public function basicHttpAuthenticationProvider() + { + return array( + array( + Bitbucket::OAUTH2_ACCESS_TOKEN_URL, + 'bitbucket.org', + array( + 'username' => 'x-token-auth', + 'password' => 'my_password' + ) + ), + array( + 'https://some-api.url.com', + 'some-api.url.com', + array( + 'username' => 'my_username', + 'password' => 'my_password' + ) + ), + ); + } + + /** + * @dataProvider basicHttpAuthenticationProvider + * + * @param string $url + * @param string $origin + * @param array $auth + */ + public function testAddAuthenticationHeaderWithBasicHttpAuthentication($url, $origin, $auth) + { + $headers = array( + 'Accept-Encoding: gzip', + 'Connection: close' + ); + + $this->expectsAuthentication($origin, $auth); + + $this->config->expects($this->once()) + ->method('get') + ->with('gitlab-domains') + ->willReturn(array()); + + $this->io->expects($this->once()) + ->method('writeError') + ->with( + 'Using HTTP basic authentication with username "' . $auth['username'] . '"', + true, + IOInterface::DEBUG + ); + + $expectedHeaders = array_merge( + $headers, + array('Authorization: Basic ' . base64_encode($auth['username'] . ':' . $auth['password'])) + ); + + $this->assertSame( + $expectedHeaders, + $this->authHelper->addAuthenticationHeader($headers, $origin, $url) + ); + } + + /** + * @dataProvider bitbucketPublicUrlProvider + * + * @param string $url + */ + public function testIsPublicBitBucketDownloadWithBitbucketPublicUrl($url) + { + $this->assertTrue($this->authHelper->isPublicBitBucketDownload($url)); + } + + public function testIsPublicBitBucketDownloadWithNonBitbucketPublicUrl() + { + $this->assertFalse($this->authHelper->isPublicBitBucketDownload( + 'https://bitbucket.org/site/oauth2/authorize') + ); + } + + public function testStoreAuthAutomatically() + { + $origin = 'github.com'; + $storeAuth = true; + $auth = array( + 'username' => 'my_username', + 'password' => 'my_password' + ); + + /** @var \Composer\Config\ConfigSourceInterface|\PHPUnit_Framework_MockObject_MockObject $configSource */ + $configSource = $this + ->getMockBuilder('Composer\Config\ConfigSourceInterface') + ->disableOriginalConstructor() + ->getMock(); + + $this->config->expects($this->once()) + ->method('getAuthConfigSource') + ->willReturn($configSource); + + $this->io->expects($this->once()) + ->method('getAuthentication') + ->with($origin) + ->willReturn($auth); + + $configSource->expects($this->once()) + ->method('addConfigSetting') + ->with('http-basic.'.$origin, $auth) + ->willReturn($configSource); + + $this->authHelper->storeAuth($origin, $storeAuth); + } + + public function testStoreAuthWithPromptYesAnswer() + { + $origin = 'github.com'; + $storeAuth = 'prompt'; + $auth = array( + 'username' => 'my_username', + 'password' => 'my_password' + ); + $answer = 'y'; + $configSourceName = 'https://api.gitlab.com/source'; + + /** @var \Composer\Config\ConfigSourceInterface|\PHPUnit_Framework_MockObject_MockObject $configSource */ + $configSource = $this + ->getMockBuilder('Composer\Config\ConfigSourceInterface') + ->disableOriginalConstructor() + ->getMock(); + + $this->config->expects($this->once()) + ->method('getAuthConfigSource') + ->willReturn($configSource); + + $configSource->expects($this->once()) + ->method('getName') + ->willReturn($configSourceName); + + $this->io->expects($this->once()) + ->method('askAndValidate') + ->with( + 'Do you want to store credentials for '.$origin.' in '.$configSourceName.' ? [Yn] ', + $this->anything(), + null, + 'y' + ) + ->willReturnCallback(function ($question, $validator, $attempts, $default) use ($answer) { + + $validator($answer); + + return $answer; + }); + + $this->io->expects($this->once()) + ->method('getAuthentication') + ->with($origin) + ->willReturn($auth); + + $configSource->expects($this->once()) + ->method('addConfigSetting') + ->with('http-basic.'.$origin, $auth) + ->willReturn($configSource); + + $this->authHelper->storeAuth($origin, $storeAuth); + } + + public function testStoreAuthWithPromptNoAnswer() + { + $origin = 'github.com'; + $storeAuth = 'prompt'; + $answer = 'n'; + $configSourceName = 'https://api.gitlab.com/source'; + + /** @var \Composer\Config\ConfigSourceInterface|\PHPUnit_Framework_MockObject_MockObject $configSource */ + $configSource = $this + ->getMockBuilder('Composer\Config\ConfigSourceInterface') + ->disableOriginalConstructor() + ->getMock(); + + $this->config->expects($this->once()) + ->method('getAuthConfigSource') + ->willReturn($configSource); + + $configSource->expects($this->once()) + ->method('getName') + ->willReturn($configSourceName); + + $this->io->expects($this->once()) + ->method('askAndValidate') + ->with( + 'Do you want to store credentials for '.$origin.' in '.$configSourceName.' ? [Yn] ', + $this->anything(), + null, + 'y' + ) + ->willReturnCallback(function ($question, $validator, $attempts, $default) use ($answer) { + + $validator($answer); + + return $answer; + }); + + $this->authHelper->storeAuth($origin, $storeAuth); + } + + /** + * @expectedException RuntimeException + */ + public function testStoreAuthWithPromptInvalidAnswer() + { + $origin = 'github.com'; + $storeAuth = 'prompt'; + $answer = 'invalid'; + $configSourceName = 'https://api.gitlab.com/source'; + + /** @var \Composer\Config\ConfigSourceInterface|\PHPUnit_Framework_MockObject_MockObject $configSource */ + $configSource = $this + ->getMockBuilder('Composer\Config\ConfigSourceInterface') + ->disableOriginalConstructor() + ->getMock(); + + $this->config->expects($this->once()) + ->method('getAuthConfigSource') + ->willReturn($configSource); + + $configSource->expects($this->once()) + ->method('getName') + ->willReturn($configSourceName); + + $this->io->expects($this->once()) + ->method('askAndValidate') + ->with( + 'Do you want to store credentials for '.$origin.' in '.$configSourceName.' ? [Yn] ', + $this->anything(), + null, + 'y' + ) + ->willReturnCallback(function ($question, $validator, $attempts, $default) use ($answer) { + + $validator($answer); + + return $answer; + }); + + $this->authHelper->storeAuth($origin, $storeAuth); + } + + /** + * @param $origin + * @param $auth + */ + private function expectsAuthentication($origin, $auth) + { + $this->io->expects($this->once()) + ->method('hasAuthentication') + ->with($origin) + ->willReturn(true); + + $this->io->expects($this->once()) + ->method('getAuthentication') + ->with($origin) + ->willReturn($auth); + } +}