123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- <?php
- /*
- * This file is part of Composer.
- *
- * (c) Nils Adermann <naderman@naderman.de>
- * Jordi Boggiano <j.boggiano@seld.be>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- namespace Composer\Util;
- use Composer\Factory;
- use Composer\IO\IOInterface;
- use Composer\Config;
- use Composer\Downloader\TransportException;
- /**
- * @author Jordi Boggiano <j.boggiano@seld.be>
- */
- class GitHub
- {
- /** @var IOInterface */
- protected $io;
- /** @var Config */
- protected $config;
- /** @var ProcessExecutor */
- protected $process;
- /** @var HttpDownloader */
- protected $httpDownloader;
- /**
- * Constructor.
- *
- * @param IOInterface $io The IO instance
- * @param Config $config The composer configuration
- * @param ProcessExecutor $process Process instance, injectable for mocking
- * @param HttpDownloader $httpDownloader Remote Filesystem, injectable for mocking
- */
- public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, HttpDownloader $httpDownloader = null)
- {
- $this->io = $io;
- $this->config = $config;
- $this->process = $process ?: new ProcessExecutor($io);
- $this->httpDownloader = $httpDownloader ?: Factory::createHttpDownloader($this->io, $config);
- }
- /**
- * Attempts to authorize a GitHub domain via OAuth
- *
- * @param string $originUrl The host this GitHub instance is located at
- * @return bool true on success
- */
- public function authorizeOAuth($originUrl)
- {
- if (!in_array($originUrl, $this->config->get('github-domains'))) {
- return false;
- }
- // if available use token from git config
- if (0 === $this->process->execute('git config github.accesstoken', $output)) {
- $this->io->setAuthentication($originUrl, trim($output), 'x-oauth-basic');
- return true;
- }
- return false;
- }
- /**
- * Authorizes a GitHub domain interactively via OAuth
- *
- * @param string $originUrl The host this GitHub instance is located at
- * @param string $message The reason this authorization is required
- * @throws \RuntimeException
- * @throws TransportException|\Exception
- * @return bool true on success
- */
- public function authorizeOAuthInteractively($originUrl, $message = null)
- {
- if ($message) {
- $this->io->writeError($message);
- }
- $note = 'Composer';
- if ($this->config->get('github-expose-hostname') === true && 0 === $this->process->execute('hostname', $output)) {
- $note .= ' on ' . trim($output);
- }
- $note .= ' ' . date('Y-m-d Hi');
- $url = 'https://'.$originUrl.'/settings/tokens/new?scopes=repo&description=' . str_replace('%20', '+', rawurlencode($note));
- $this->io->writeError(sprintf('Head to %s', $url));
- $this->io->writeError(sprintf('to retrieve a token. It will be stored in "%s" for future use by Composer.', $this->config->getAuthConfigSource()->getName()));
- $token = trim($this->io->askAndHideAnswer('Token (hidden): '));
- if (!$token) {
- $this->io->writeError('<warning>No token given, aborting.</warning>');
- $this->io->writeError('You can also add it manually later by using "composer config --global --auth github-oauth.github.com <token>"');
- return false;
- }
- $this->io->setAuthentication($originUrl, $token, 'x-oauth-basic');
- try {
- $apiUrl = ('github.com' === $originUrl) ? 'api.github.com/' : $originUrl . '/api/v3/';
- $this->httpDownloader->get('https://'. $apiUrl, array(
- 'retry-auth-failure' => false,
- ));
- } catch (TransportException $e) {
- if (in_array($e->getCode(), array(403, 401))) {
- $this->io->writeError('<error>Invalid token provided.</error>');
- $this->io->writeError('You can also add it manually later by using "composer config --global --auth github-oauth.github.com <token>"');
- return false;
- }
- throw $e;
- }
- // store value in user config
- $this->config->getConfigSource()->removeConfigSetting('github-oauth.'.$originUrl);
- $this->config->getAuthConfigSource()->addConfigSetting('github-oauth.'.$originUrl, $token);
- $this->io->writeError('<info>Token stored successfully.</info>');
- return true;
- }
- /**
- * Extract ratelimit from response.
- *
- * @param array $headers Headers from Composer\Downloader\TransportException.
- *
- * @return array Associative array with the keys limit and reset.
- */
- public function getRateLimit(array $headers)
- {
- $rateLimit = array(
- 'limit' => '?',
- 'reset' => '?',
- );
- foreach ($headers as $header) {
- $header = trim($header);
- if (false === strpos($header, 'X-RateLimit-')) {
- continue;
- }
- list($type, $value) = explode(':', $header, 2);
- switch ($type) {
- case 'X-RateLimit-Limit':
- $rateLimit['limit'] = (int) trim($value);
- break;
- case 'X-RateLimit-Reset':
- $rateLimit['reset'] = date('Y-m-d H:i:s', (int) trim($value));
- break;
- }
- }
- return $rateLimit;
- }
- /**
- * Finds whether a request failed due to rate limiting
- *
- * @param array $headers Headers from Composer\Downloader\TransportException.
- *
- * @return bool
- */
- public function isRateLimited(array $headers)
- {
- foreach ($headers as $header) {
- if (preg_match('{^X-RateLimit-Remaining: *0$}i', trim($header))) {
- return true;
- }
- }
- return false;
- }
- }
|