From 9ce4076a2cd6fcbb48ac526801649fe775174b77 Mon Sep 17 00:00:00 2001 From: Jean-Michel Leclercq Date: Wed, 13 Oct 2021 19:13:54 +0200 Subject: [PATCH] #51 Support access token auth (#55) #51 Support access token auth --- README.md | 1 + src/Configuration.php | 9 +++ src/PushCommand.php | 1 + src/RepositoryProvider/AbstractProvider.php | 59 ++++++++++++++++--- .../ArtifactoryProvider.php | 25 ++------ src/RepositoryProvider/NexusProvider.php | 22 ++----- tests/ConfigurationTest.php | 7 +++ .../RepositoryProvider/NexusProviderTest.php | 38 +++++++++++- 8 files changed, 116 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 738a812..c5576d8 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Many of the options are optional since they can be added directly to the `compos [--src-url=]\ [--src-ref=]\ [--ssl-verify=true/]\ + [--access-token=] # Example diff --git a/src/Configuration.php b/src/Configuration.php index 5b1d652..7b394c9 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -161,6 +161,15 @@ public function getType() return $type; } + /** + * Return the access token + * @return string + */ + public function getAccessToken() + { + return $this->input->getOption('access-token'); + } + /** * @return boolean */ diff --git a/src/PushCommand.php b/src/PushCommand.php index b2f9ab4..c20bc8f 100644 --- a/src/PushCommand.php +++ b/src/PushCommand.php @@ -67,6 +67,7 @@ protected function configure() new InputOption('keep-vendor', null, InputOption::VALUE_NONE, 'Keep vendor directory when creating zip'), new InputOption('keep-dot-files', null, InputOption::VALUE_NONE, 'Keep dots files/dirs when creating zip'), new InputOption('ssl-verify', null, InputOption::VALUE_OPTIONAL, 'Enable (true) or disable (false) the SSL verification'), + new InputOption('access-token', null, InputOption::VALUE_OPTIONAL, 'Access Token to get authenticated with'), ]) ->setHelp( <<getConfiguration()->getAccessToken()) { + $credentials['access_token']['token'] = $this->getConfiguration()->getAccessToken(); + } if (preg_match( '{^(?:https?)://([^/]+)(?:/.*)?}', @@ -93,15 +96,15 @@ public function sendFile( ); try { - if (empty($credential) || empty($credential['username']) || empty($credential['password'])) { + if (!empty($credential['token'])) { $this->getIO() ->write( - '[postFile] Use no credentials', + '[postFile] Use ' . $type, true, IOInterface::VERY_VERBOSE ); - $this->postFile($filePath); - } else { + $this->postFileWithToken($filePath, $credential['token']); + } elseif (!empty($credential['username']) && !empty($credential['password'])) { $this->getIO() ->write( '[postFile] Use user ' . $credential['username'], @@ -113,6 +116,14 @@ public function sendFile( $credential['username'], $credential['password'] ); + } else { + $this->getIO() + ->write( + '[postFile] Use no credentials', + true, + IOInterface::VERY_VERBOSE + ); + $this->postFile($filePath); } return; @@ -149,13 +160,45 @@ public function sendFile( } /** - * Post the given file + * Process the API call + * @param $file file to upload + * @param $options http call options + */ + abstract protected function apiCall($file, $options); + + /** + * The file has to be uploaded by hand because of composer limitations + * (impossible to use Guzzle functions.php file in a composer plugin). + * + * @param $file + * @param $username + * @param $password + * + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function postFile($file, $username = null, $password = null) + { + $options = []; + + if (!empty($username) && !empty($password)) { + $options['auth'] = [$username, $password]; + } + + $this->apiCall($file, $options); + } + + /** + * Post the given file with access token * @param $file - * @param null $username - * @param null $password + * @param string $token * @return mixed */ - abstract protected function postFile($file, $username = null, $password = null); + protected function postFileWithToken($file, $token) + { + $options = []; + $options['headers']['Authorization'] = 'Bearer ' . $token; + $this->apiCall($file, $options); + } /** * @return Configuration diff --git a/src/RepositoryProvider/ArtifactoryProvider.php b/src/RepositoryProvider/ArtifactoryProvider.php index 4658b1d..2608777 100644 --- a/src/RepositoryProvider/ArtifactoryProvider.php +++ b/src/RepositoryProvider/ArtifactoryProvider.php @@ -32,28 +32,15 @@ public function getUrl() } /** - * The file has to be uploaded by hand because of composer limitations - * (impossible to use Guzzle functions.php file in a composer plugin). - * - * @param $file - * @param $username - * @param $password - * - * @throws \GuzzleHttp\Exception\GuzzleException + * Process the API call + * @param $file file to upload + * @param $options http call options */ - protected function postFile($file, $username = null, $password = null) + protected function apiCall($file, $options) { + $options['debug'] = $this->getIO()->isVeryVerbose(); + $options['body'] = fopen($file, 'r'); $url = $this->getUrl() . '.' . pathinfo($file, PATHINFO_EXTENSION) . '?properties=composer.version=' . $this->getConfiguration()->getVersion(); - - $options = [ - 'debug' => $this->getIO()->isVeryVerbose(), - 'body' => fopen($file, 'r') - ]; - - if (!empty($username) && !empty($password)) { - $options['auth'] = [$username, $password]; - } - $this->getClient()->request('PUT', $url, $options); } } diff --git a/src/RepositoryProvider/NexusProvider.php b/src/RepositoryProvider/NexusProvider.php index 172d757..7d9d3f9 100644 --- a/src/RepositoryProvider/NexusProvider.php +++ b/src/RepositoryProvider/NexusProvider.php @@ -29,16 +29,11 @@ public function getUrl() } /** - * The file has to be uploaded by hand because of composer limitations - * (impossible to use Guzzle functions.php file in a composer plugin). - * - * @param $file - * @param $username - * @param $password - * - * @throws \GuzzleHttp\Exception\GuzzleException + * Process the API call + * @param $file file to upload + * @param $options http call options */ - protected function postFile($file, $username = null, $password = null) + protected function apiCall($file, $options) { $url = $this->getUrl(); @@ -46,9 +41,8 @@ protected function postFile($file, $username = null, $password = null) $sourceUrl = $this->getConfiguration()->getSourceUrl(); $sourceReference = $this->getConfiguration()->getSourceReference(); - $options = [ - 'debug' => $this->getIO()->isVeryVerbose(), - ]; + $options['debug'] = $this->getIO()->isVeryVerbose(); + if (!empty($sourceType) && !empty($sourceUrl) && !empty($sourceReference)) { $options['multipart'] = [ [ @@ -73,10 +67,6 @@ protected function postFile($file, $username = null, $password = null) $options['body'] = fopen($file, 'r'); } - if (!empty($username) && !empty($password)) { - $options['auth'] = [$username, $password]; - } - $this->getClient()->request('PUT', $url, $options); } } diff --git a/tests/ConfigurationTest.php b/tests/ConfigurationTest.php index 5f5b05c..daf11d3 100644 --- a/tests/ConfigurationTest.php +++ b/tests/ConfigurationTest.php @@ -81,6 +81,11 @@ public function testGetSourceType() $this->assertEquals('my-src-type', $this->configuration->getSourceType()); } + public function testGetAccessToken() + { + $this->assertEquals('my-token', $this->configuration->getAccessToken()); + } + public function testGetUrl() { $this->assertEquals('https://option-url.com', $this->configuration->getUrl()); @@ -267,6 +272,8 @@ private function createInputMock() return $this->configType; case 'ssl-verify': return $this->configVerifySsl; + case 'access-token': + return 'my-token'; } }); diff --git a/tests/RepositoryProvider/NexusProviderTest.php b/tests/RepositoryProvider/NexusProviderTest.php index 5d3506f..d6632b4 100644 --- a/tests/RepositoryProvider/NexusProviderTest.php +++ b/tests/RepositoryProvider/NexusProviderTest.php @@ -166,14 +166,15 @@ public function testSendFileWithMultipleCredentials() $configurationMock->method('get')->willReturnCallback(function($parameter) { switch($parameter) { case 'username': - return 'admin'; + return ''; case 'password': - return 'my-password'; + return ''; + case 'access-token': + return ''; } }); $mock = new MockHandler([ - new Response(401), new Response(200) ]); $handlerStack = HandlerStack::create($mock); @@ -223,6 +224,37 @@ public function testSendFileWithBadCredentials() $nexusProvider->sendFile($this->getFilePath()); } + /** + * @covers \Elendev\ComposerPush\RepositoryProvider\NexusProvider::sendFile + */ + public function testSendFileWithAccessToken() + { + $configurationMock = $this->createBaseConfigurationMock(); + $ioMock = $this->createMock(IOInterface::class); + $configurationMock->method('getAccessToken')->willReturn('my-token'); + + $mock = new MockHandler([ + new Response(200) + ]); + $handlerStack = HandlerStack::create($mock); + $client = new Client(['handler' => $handlerStack]); + + $nexusProvider = new NexusProvider($configurationMock, $ioMock, $client); + + $nexusProvider->sendFile($this->getFilePath()); + + $request = $mock->getLastRequest(); + + $this->assertEquals('https', $request->getUri()->getScheme()); + $this->assertEquals('example.com', $request->getUri()->getHost()); + $this->assertEquals('/my-repository/packages/upload/my-package/2.1.0', $request->getUri()->getPath()); + $this->assertEquals('PUT', $request->getMethod()); + + $this->assertEquals('Bearer my-token', $request->getHeader('Authorization')[0]); + + $this->assertEquals('Simple test file to push.', $request->getBody()->getContents()); + } + /** * Create a base configuration mock * @return Configuration|\PHPUnit\Framework\MockObject\MockObject