*/ 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('When working with _public_ GitHub repositories only, head here to retrieve a token:'); $this->io->writeError($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('When you need to access _private_ GitHub repositories as well, go to:'); $this->io->writeError($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 === stripos($header, 'x-ratelimit-')) { continue; } [$type, $value] = explode(':', $header, 2); switch (strtolower($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:----fV3Gx3P9IUSLdt7//21vnI3gR7z6DMHX0mKS8teCtttmvI2vWny0LWgaQ6haM/tRlfaEF/ggszUIttaDuQidA01evqGq0UsZYk0862M0E1t/iQFN2Z+wjqg3hWibMgoanCfVOjhAosygKrThjaHauWV1sD/wdH8tbhhSbhNkWVdqQq5WT8HsfyKPqdJ1npcITVxCTMi0aBfV7sbXuK+INd6sNwWY3FDQ0fXWAfKm0Rt+P3GIAYkKVj/8eST6faFxHVll8Hx9dHmlTNecjDw8tAxd89KIjqUX+R9SZZH38old2gPIM/4lU0dGJ9ijDfT6aMtUgViYyS0XjDRyN0OKfvYVNLOf9rjn1rBlq99QjVybJERiO4vGMW5zJt80liU2XzOw8U00pnHYu6e7QdFl6aJZgS3RY6JrDAEhORj0FNPJRJhK6OtNqG2uqb8ZbWlCmEY2fuDD8wDPW+ga3VTQRgHVXsSYzdB5QI4nw+3BVkIGKmWLMZIW/LAx5B1A/IhesYrGeXJUiKS0itiNLsCJ4mUAXzxnWxm/OqoYIDaqIdhBv0QGD3SWYfoA/wC6JBhOjyhMU/i6Au4SOUfdsYOaixWe5m8HqOrfWWohBCGmFifKivl2qTXet4EkzSgd6S3v4MVHbmCALCw9a+g1Oc1Z0WGpQp31DpNHRI2ZgXclS2Y=----ATTACHMENT:----MTIxOTYyMDczNTIyMjIxMiA4NDYyODM4NDUyMjQwNjM5IDI0OTAxNTM3MzE5ODM2NTI=