diff --git a/src/Composer/Util/GitLab.php b/src/Composer/Util/GitLab.php index 524d8a3ef..c94be6121 100644 --- a/src/Composer/Util/GitLab.php +++ b/src/Composer/Util/GitLab.php @@ -39,15 +39,16 @@ class GitLab { $this->io = $io; $this->config = $config; - $this->process = $process ?: new ProcessExecutor; + $this->process = $process ?: new ProcessExecutor(); $this->remoteFilesystem = $remoteFilesystem ?: new RemoteFilesystem($io, $config); } /** - * Attempts to authorize a GitLab domain via OAuth + * Attempts to authorize a GitLab domain via OAuth. * - * @param string $originUrl The host this GitLab instance is located at - * @return bool true on success + * @param string $originUrl The host this GitLab instance is located at + * + * @return bool true on success */ public function authorizeOAuth($originUrl) { @@ -66,13 +67,15 @@ class GitLab } /** - * Authorizes a GitLab domain interactively via OAuth + * Authorizes a GitLab domain interactively via OAuth. + * + * @param string $originUrl The host this GitLab instance is located at + * @param string $message The reason this authorization is required * - * @param string $originUrl The host this GitLab instance is located at - * @param string $message The reason this authorization is required * @throws \RuntimeException * @throws TransportException|\Exception - * @return bool true on success + * + * @return bool true on success */ public function authorizeOAuthInteractively($originUrl, $message = null) { @@ -80,9 +83,8 @@ class GitLab $this->io->writeError($message); } - $this->io->writeError(sprintf('A token will be created and stored in "%s", your password will never be stored', $this->config->getAuthConfigSource()->getName())); - $this->io->writeError('To revoke access to this token you can visit ' . $originUrl . '/profile/applications'); + $this->io->writeError('To revoke access to this token you can visit '.$originUrl.'/profile/applications'); $attemptCounter = 0; @@ -93,15 +95,14 @@ class GitLab // 401 is bad credentials, // 403 is max login attempts exceeded if (in_array($e->getCode(), array(403, 401))) { - if (401 === $e->getCode()) { $this->io->writeError('Bad credentials.'); } else { $this->io->writeError('Maximum number of login attempts exceeded. Please try again later.'); } - $this->io->writeError('You can also manually create a personal token at ' . $originUrl . '/profile/applications'); - $this->io->writeError('Add it using "composer config gitlab-oauth.' . $originUrl . ' "'); + $this->io->writeError('You can also manually create a personal token at '.$originUrl.'/profile/applications'); + $this->io->writeError('Add it using "composer config gitlab-oauth.'.$originUrl.' "'); continue; } @@ -110,14 +111,14 @@ class GitLab } $this->io->setAuthentication($originUrl, $response['access_token'], 'oauth2'); - $this->config->getConfigSource()->removeConfigSetting('gitlab-oauth.'.$originUrl); - // store value in user config + + // store value in user config in auth file $this->config->getAuthConfigSource()->addConfigSetting('gitlab-oauth.'.$originUrl, $response['access_token']); return true; } - throw new \RuntimeException("Invalid GitLab credentials 5 times in a row, aborting."); + throw new \RuntimeException('Invalid GitLab credentials 5 times in a row, aborting.'); } private function createToken($originUrl) @@ -127,10 +128,10 @@ class GitLab $headers = array('Content-Type: application/x-www-form-urlencoded'); - $apiUrl = $originUrl ; + $apiUrl = $originUrl; $data = http_build_query(array( - 'username' => $username, - 'password' => $password, + 'username' => $username, + 'password' => $password, 'grant_type' => 'password', )); $options = array( @@ -138,11 +139,11 @@ class GitLab 'http' => array( 'method' => 'POST', 'header' => $headers, - 'content' => $data + 'content' => $data, ), ); - $json = $this->remoteFilesystem->getContents($originUrl, 'http://'. $apiUrl . '/oauth/token', false, $options); + $json = $this->remoteFilesystem->getContents($originUrl, 'http://'.$apiUrl.'/oauth/token', false, $options); $this->io->writeError('Token successfully created'); diff --git a/tests/Composer/Test/Util/GitLabTest.php b/tests/Composer/Test/Util/GitLabTest.php new file mode 100644 index 000000000..31ac52080 --- /dev/null +++ b/tests/Composer/Test/Util/GitLabTest.php @@ -0,0 +1,159 @@ + +* 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\Downloader\TransportException; +use Composer\Util\GitLab; + +/** + * @author Jérôme Tamarelle + */ +class GitLabTest extends \PHPUnit_Framework_TestCase +{ + private $username = 'username'; + private $password = 'password'; + private $authcode = 'authcode'; + private $message = 'mymessage'; + private $origin = 'gitlab.com'; + private $token = 'gitlabtoken'; + + public function testUsernamePasswordAuthenticationFlow() + { + $io = $this->getIOMock(); + $io + ->expects($this->at(0)) + ->method('writeError') + ->with($this->message) + ; + $io + ->expects($this->once()) + ->method('ask') + ->with('Username: ') + ->willReturn($this->username) + ; + $io + ->expects($this->once()) + ->method('askAndHideAnswer') + ->with('Password: ') + ->willReturn($this->password) + ; + + $rfs = $this->getRemoteFilesystemMock(); + $rfs + ->expects($this->once()) + ->method('getContents') + ->with( + $this->equalTo($this->origin), + $this->equalTo(sprintf('http://%s/oauth/token', $this->origin)), + $this->isFalse(), + $this->anything() + ) + ->willReturn(sprintf('{"access_token": "%s", "token_type": "bearer", "expires_in": 7200}', $this->token)) + ; + + $config = $this->getConfigMock(); + $config + ->expects($this->exactly(2)) + ->method('getAuthConfigSource') + ->willReturn($this->getAuthJsonMock()) + ; + + $gitLab = new GitLab($io, $config, null, $rfs); + + $this->assertTrue($gitLab->authorizeOAuthInteractively($this->origin, $this->message)); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Invalid GitLab credentials 5 times in a row, aborting. + */ + public function testUsernamePasswordFailure() + { + $io = $this->getIOMock(); + $io + ->expects($this->exactly(5)) + ->method('ask') + ->with('Username: ') + ->willReturn($this->username) + ; + $io + ->expects($this->exactly(5)) + ->method('askAndHideAnswer') + ->with('Password: ') + ->willReturn($this->password) + ; + + $rfs = $this->getRemoteFilesystemMock(); + $rfs + ->expects($this->exactly(5)) + ->method('getContents') + ->will($this->throwException(new TransportException('', 401))) + ; + + $config = $this->getConfigMock(); + $config + ->expects($this->exactly(1)) + ->method('getAuthConfigSource') + ->willReturn($this->getAuthJsonMock()) + ; + + $gitLab = new GitLab($io, $config, null, $rfs); + + $gitLab->authorizeOAuthInteractively($this->origin); + } + + 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; + } + + private function getAuthJsonMock() + { + $authjson = $this + ->getMockBuilder('Composer\Config\JsonConfigSource') + ->disableOriginalConstructor() + ->getMock() + ; + $authjson + ->expects($this->atLeastOnce()) + ->method('getName') + ->willReturn('auth.json') + ; + + return $authjson; + } +}