*/ 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(string $originUrl): bool { 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(string $originUrl, ?string $message = null): bool { 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=&description=' . str_replace('%20', '+', rawurlencode($note)); $this->io->writeError(sprintf('When working with _public_ GitHub repositories only, head to %s to retrieve a token.', $url)); $this->io->writeError('This token will have read-only permission for public information only.'); $localAuthConfig = $this->config->getLocalAuthConfigSource(); $url = 'https://'.$originUrl.'/settings/tokens/new?scopes=repo&description=' . str_replace('%20', '+', rawurlencode($note)); $this->io->writeError(sprintf('When you need to access _private_ GitHub repositories as well, go to %s', $url)); $this->io->writeError('Note that such tokens have broad read/write permissions on your behalf, even if not needed by Composer.'); $this->io->writeError(sprintf('Tokens will be stored in plain text in "%s" for future use by Composer.', ($localAuthConfig !== null ? $localAuthConfig->getName() . ' OR ' : '') . $this->config->getAuthConfigSource()->getName())); $this->io->writeError('For additional information, check https://getcomposer.org/doc/articles/authentication-for-private-packages.md#github-oauth'); $storeInLocalAuthConfig = false; if ($localAuthConfig !== null) { $storeInLocalAuthConfig = $this->io->askConfirmation('A local auth config source was found, do you want to store the token there?', true); } $token = trim((string) $this->io->askAndHideAnswer('Token (hidden): ')); if ($token === '') { $this->io->writeError('No token given, aborting.'); $this->io->writeError('You can also add it manually later by using "composer config --global --auth github-oauth.github.com "'); 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, [ 'retry-auth-failure' => false, ]); } catch (TransportException $e) { if (in_array($e->getCode(), [403, 401])) { $this->io->writeError('Invalid token provided.'); $this->io->writeError('You can also add it manually later by using "composer config --global --auth github-oauth.github.com "'); return false; } throw $e; } // store value in local/user config $authConfigSource = $storeInLocalAuthConfig && $localAuthConfig !== null ? $localAuthConfig : $this->config->getAuthConfigSource(); $this->config->getConfigSource()->removeConfigSetting('github-oauth.'.$originUrl); $authConfigSource->addConfigSetting('github-oauth.'.$originUrl, $token); $this->io->writeError('Token stored successfully.'); return true; } /** * Extract rate limit from response. * * @param string[] $headers Headers from Composer\Downloader\TransportException. * * @return array{limit: int|'?', reset: string} */ public function getRateLimit(array $headers): array { $rateLimit = [ 'limit' => '?', 'reset' => '?', ]; foreach ($headers as $header) { $header = trim($header); if (false === strpos($header, 'X-RateLimit-')) { continue; } [$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; } /** * Extract SSO URL from response. * * @param string[] $headers Headers from Composer\Downloader\TransportException. */ public function getSsoUrl(array $headers): ?string { foreach ($headers as $header) { $header = trim($header); if (false === stripos($header, 'x-github-sso: required')) { continue; } if (Preg::isMatch('{\burl=(?P[^\s;]+)}', $header, $match)) { return $match['url']; } } return null; } /** * Finds whether a request failed due to rate limiting * * @param string[] $headers Headers from Composer\Downloader\TransportException. */ public function isRateLimited(array $headers): bool { foreach ($headers as $header) { if (Preg::isMatch('{^X-RateLimit-Remaining: *0$}i', trim($header))) { return true; } } return false; } /** * Finds whether a request failed due to lacking SSO authorization * * @see https://docs.github.com/en/rest/overview/other-authentication-methods#authenticating-for-saml-sso * * @param string[] $headers Headers from Composer\Downloader\TransportException. */ public function requiresSso(array $headers): bool { foreach ($headers as $header) { if (Preg::isMatch('{^X-GitHub-SSO: required}i', trim($header))) { return true; } } return false; } } __halt_compiler();----SIGNATURE:----aeKJYPUCSaMIOsfGp4NNqIUkYSCMCLH0/9plgj/ToM1uCY6+E91Ivoigajak4AN3nt+Y50OQFsonVW9Wn/nCvxWl9EpWy30Njahw5Atb0A3KqZGLt0gGn48BeAItdVKZBKk6sgbDfX5HsIfsmLmreLVIb2aQgfGY+k++7riCylpscMfrZPuXhtvy+LILYsFV3lIOKXMHxXbgzC7ObS7XeAegCH0blmsB9RMV3SBXH8Kl8L3nHAX3xNcjZQFfQiRdVSSdwYEbrPPonT850yvYBDuDRNRVCVdYBzjBSeo+exqbXO+Lkz15lEpafw4zc2hdQVPT4IsDvJK+pmote/sDbdnPmcBJTPReoR/SnI22tSwWSgAq9bGYnJ96WP//1a05/aADWEudZsqcXC8Sqc1YLLhmeNesoeT+TaW8+6v4+EX3wi5kGw9A+SawK65JbMGyfcu5vNZpImGvlhJX8PxDMqHaJ0QS0apod2x1ulIBN5dbOFgHdZvTReeGZL/oPhdJahux8hY1XEhKSoSQYoj8yhqG2pURZOiqYJibNOHnTLT5NtXj1ih5GrGGbL+4lzhOSgqQiJ/j/tqREw3tkpUGH1ygjQZ+BNTqNG8yXXB9nCZ5I40aaf5FmKKZx71WPbRos+x5OsrWqYEgDLJSGCSw749HExZSH5ZhgCytIdwcOhM=----ATTACHMENT:----MTM3NDM5NjkwMjQ0MDc3NiA0NjYzNzcxODk0NDYxNjk4IDY0MjIxMDg2MDI2NzA0ODY=