Merge remote-tracking branch 'cs278/github-otp-support'
commit
9db2a537e5
|
@ -684,8 +684,8 @@ The following options are supported:
|
|||
`{"github.com": "oauthtoken"}` as the value of this option will use `oauthtoken`
|
||||
to access private repositories on github and to circumvent the low IP-based
|
||||
rate limiting of their API.
|
||||
[Read more](articles/troubleshooting.md#api-rate-limit-and-two-factor-authentication)
|
||||
on how to get an oauth token for GitHub.
|
||||
[Read more](articles/troubleshooting.md#api-rate-limit-and-oauth-tokens)
|
||||
on how to get an OAuth token for GitHub.
|
||||
* **vendor-dir:** Defaults to `vendor`. You can install dependencies into a
|
||||
different directory if you want to.
|
||||
* **bin-dir:** Defaults to `vendor/bin`. If a project includes binaries, they
|
||||
|
|
|
@ -105,14 +105,15 @@ Or, you can increase the limit with a command-line argument:
|
|||
or ```HKEY_CURRENT_USER\Software\Microsoft\Command Processor```.
|
||||
3. Check if it contains any path to non-existent file, if it's the case, just remove them.
|
||||
|
||||
## API rate limit and two factor authentication
|
||||
## API rate limit and OAuth tokens
|
||||
|
||||
Because of GitHub's rate limits on their API it can happen that Composer prompts
|
||||
for authentication asking your username and password so it can go ahead with its work.
|
||||
Unfortunately this will not work if you enabled two factor authentication on
|
||||
your GitHub account and to solve this issue you need to:
|
||||
|
||||
1. [Create](https://github.com/settings/applications) an oauth token on GitHub.
|
||||
If you would prefer not to provide your GitHub credentials to Composer you can
|
||||
manually create a token using the following procedure:
|
||||
|
||||
1. [Create](https://github.com/settings/applications) an OAuth token on GitHub.
|
||||
[Read more](https://github.com/blog/1509-personal-api-tokens) on this.
|
||||
|
||||
2. Add it to the configuration running `composer config -g github-oauth.github.com <oauthtoken>`
|
||||
|
|
|
@ -87,9 +87,13 @@ class GitHub
|
|||
$this->io->write('To revoke access to this token you can visit https://github.com/settings/applications');
|
||||
while ($attemptCounter++ < 5) {
|
||||
try {
|
||||
if (empty($otp) || !$this->io->hasAuthentication($originUrl)) {
|
||||
$username = $this->io->ask('Username: ');
|
||||
$password = $this->io->askAndHideAnswer('Password: ');
|
||||
$otp = null;
|
||||
|
||||
$this->io->setAuthentication($originUrl, $username, $password);
|
||||
}
|
||||
|
||||
// build up OAuth app name
|
||||
$appName = 'Composer';
|
||||
|
@ -97,11 +101,18 @@ class GitHub
|
|||
$appName .= ' on ' . trim($output);
|
||||
}
|
||||
|
||||
$headers = array('Content-Type: application/json');
|
||||
|
||||
if ($otp) {
|
||||
$headers[] = 'X-GitHub-OTP: ' . $otp;
|
||||
}
|
||||
|
||||
$contents = JsonFile::parseJson($this->remoteFilesystem->getContents($originUrl, 'https://'. $apiUrl . '/authorizations', false, array(
|
||||
'retry-auth-failure' => false,
|
||||
'http' => array(
|
||||
'method' => 'POST',
|
||||
'follow_location' => false,
|
||||
'header' => "Content-Type: application/json\r\n",
|
||||
'header' => $headers,
|
||||
'content' => json_encode(array(
|
||||
'scopes' => array('repo'),
|
||||
'note' => $appName,
|
||||
|
@ -111,6 +122,34 @@ class GitHub
|
|||
)));
|
||||
} catch (TransportException $e) {
|
||||
if (in_array($e->getCode(), array(403, 401))) {
|
||||
// 401 when authentication was supplied, handle 2FA if required.
|
||||
if ($this->io->hasAuthentication($originUrl)) {
|
||||
$headerNames = array_map(function($header) {
|
||||
return strtolower(strstr($header, ':', true));
|
||||
}, $e->getHeaders());
|
||||
|
||||
if ($key = array_search('x-github-otp', $headerNames)) {
|
||||
$headers = $e->getHeaders();
|
||||
list($required, $method) = array_map('trim', explode(';', substr(strstr($headers[$key], ':'), 1)));
|
||||
|
||||
if ('required' === $required) {
|
||||
$this->io->write('Two-factor Authentication');
|
||||
|
||||
if ('app' === $method) {
|
||||
$this->io->write('Open the two-factor authentication app on your device to view your authentication code and verify your identity.');
|
||||
}
|
||||
|
||||
if ('sms' === $method) {
|
||||
$this->io->write('You have been sent an SMS message with an authentication code to verify your identity.');
|
||||
}
|
||||
|
||||
$otp = $this->io->ask('Authentication Code: ');
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->io->write('Invalid credentials.');
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ class RemoteFilesystem
|
|||
private $progress;
|
||||
private $lastProgress;
|
||||
private $options;
|
||||
private $retryAuthFailure;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
|
@ -109,12 +110,19 @@ class RemoteFilesystem
|
|||
$this->fileName = $fileName;
|
||||
$this->progress = $progress;
|
||||
$this->lastProgress = null;
|
||||
$this->retryAuthFailure = true;
|
||||
|
||||
// capture username/password from URL if there is one
|
||||
if (preg_match('{^https?://(.+):(.+)@([^/]+)}i', $fileUrl, $match)) {
|
||||
$this->io->setAuthentication($originUrl, urldecode($match[1]), urldecode($match[2]));
|
||||
}
|
||||
|
||||
if (isset($additionalOptions['retry-auth-failure'])) {
|
||||
$this->retryAuthFailure = (bool) $additionalOptions['retry-auth-failure'];
|
||||
|
||||
unset($additionalOptions['retry-auth-failure']);
|
||||
}
|
||||
|
||||
$options = $this->getOptionsForUrl($originUrl, $additionalOptions);
|
||||
|
||||
if ($this->io->isDebug()) {
|
||||
|
@ -260,6 +268,11 @@ class RemoteFilesystem
|
|||
throw new TransportException($message, 401);
|
||||
}
|
||||
|
||||
// Bail if the caller is going to handle authentication failures itself.
|
||||
if (!$this->retryAuthFailure) {
|
||||
throw new TransportException('The "'.$this->fileUrl.'" file could not be downloaded ('.trim($message).')', 401);
|
||||
}
|
||||
|
||||
$this->promptAuthAndRetry();
|
||||
break;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue