diff --git a/Library/Homebrew/cmd/gist-logs.rb b/Library/Homebrew/cmd/gist-logs.rb index acc9a10ea244e0e1f213efef86453c2b65f9ad6a..5954c031ebc2d68cafba025067a57cea8b604cac 100644 --- a/Library/Homebrew/cmd/gist-logs.rb +++ b/Library/Homebrew/cmd/gist-logs.rb @@ -37,12 +37,12 @@ module Homebrew if ARGV.include?("--new-issue") || ARGV.switch?("n") auth = :AUTH_TOKEN - unless GitHub.api_credentials + if GitHub.api_credentials_type == :none puts "You can create a personal access token: https://github.com/settings/tokens" puts "and then set HOMEBREW_GITHUB_API_TOKEN as authentication method." puts - auth = :AUTH_BASIC + auth = :AUTH_USER_LOGIN end url = new_issue(f.tap, "#{f.name} failed to build on #{MacOS.full_version}", url, auth) @@ -118,9 +118,21 @@ module Homebrew headers = GitHub.api_headers headers["Content-Type"] = "application/json" + basic_auth_credentials = nil + if auth != :AUTH_USER_LOGIN + token, username = GitHub.api_credentials + case GitHub.api_credentials_type + when :keychain + basic_auth_credentials = [username, token] + when :environment + headers["Authorization"] = "token #{token}" + end + end + request = Net::HTTP::Post.new(path, headers) + request.basic_auth(*basic_auth_credentials) if basic_auth_credentials - login(request) if auth == :AUTH_BASIC + login(request) if auth == :AUTH_USER_LOGIN request.body = Utils::JSON.dump(data) request @@ -133,6 +145,7 @@ module Homebrew when Net::HTTPCreated Utils::JSON.load get_body(response) else + GitHub.api_credentials_error_message(response) raise "HTTP #{response.code} #{response.message} (expected 201)" end end diff --git a/Library/Homebrew/utils.rb b/Library/Homebrew/utils.rb index 1fbb5afac5d3e5deecf50ff0fc74067a8af3c942..98ebcc9c8df8ec14996e6551105141d8963ba06c 100644 --- a/Library/Homebrew/utils.rb +++ b/Library/Homebrew/utils.rb @@ -484,7 +484,7 @@ module GitHub class RateLimitExceededError < Error def initialize(reset, error) super <<-EOS.undent - GitHub #{error} + GitHub API Error: #{error} Try again in #{pretty_ratelimit_reset(reset)}, or create a personal access token: #{Tty.em}https://github.com/settings/tokens/new?scopes=&description=Homebrew#{Tty.reset} and then set the token as: export HOMEBREW_GITHUB_API_TOKEN="your_new_token" @@ -506,9 +506,12 @@ module GitHub EOS else message << <<-EOS.undent - The GitHub credentials in the OS X keychain are invalid. + The GitHub credentials in the OS X keychain may be invalid. Clear them with: printf "protocol=https\\nhost=github.com\\n" | git credential-osxkeychain erase + Or create a personal access token: + #{Tty.em}https://github.com/settings/tokens/new?scopes=&description=Homebrew#{Tty.reset} + and then set the token as: export HOMEBREW_GITHUB_API_TOKEN="your_new_token" EOS end super message @@ -536,32 +539,71 @@ module GitHub end end - def api_headers - @api_headers ||= begin - headers = { - "User-Agent" => HOMEBREW_USER_AGENT, - "Accept" => "application/vnd.github.v3+json" - } - token, username = api_credentials - if token && !token.empty? - if username && !username.empty? - headers[:http_basic_authentication] = [username, token] - else - headers["Authorization"] = "token #{token}" + def api_credentials_type + token, username = api_credentials + if token && !token.empty? + if username && !username.empty? + :keychain + else + :environment + end + else + :none + end + end + + def api_credentials_error_message(response_headers) + @api_credentials_error_message_printed ||= begin + unauthorized = (response_headers["status"] == "401 Unauthorized") + scopes = response_headers["x-accepted-oauth-scopes"].to_s.split(", ") + if !unauthorized && scopes.empty? + credentials_scopes = response_headers["x-oauth-scopes"].to_s.split(", ") + + case GitHub.api_credentials_type + when :keychain + onoe <<-EOS.undent + Your OS X keychain GitHub credentials do not have sufficient scope! + Scopes they have: #{credentials_scopes} + Create a personal access token: https://github.com/settings/tokens + and then set HOMEBREW_GITHUB_API_TOKEN as the authentication method instead. + EOS + when :environment + onoe <<-EOS.undent + Your HOMEBREW_GITHUB_API_TOKEN does not have sufficient scope! + Scopes it has: #{credentials_scopes} + Create a new personal access token: https://github.com/settings/tokens + and then set the new HOMEBREW_GITHUB_API_TOKEN as the authentication method instead. + EOS end end - headers + true end end + def api_headers + { + "User-Agent" => HOMEBREW_USER_AGENT, + "Accept" => "application/vnd.github.v3+json" + } + end + def open(url, &_block) # This is a no-op if the user is opting out of using the GitHub API. return if ENV["HOMEBREW_NO_GITHUB_API"] require "net/https" + headers = api_headers + token, username = api_credentials + case api_credentials_type + when :keychain + headers[:http_basic_authentication] = [username, token] + when :environment + headers["Authorization"] = "token #{token}" + end + begin - Kernel.open(url, api_headers) { |f| yield Utils::JSON.load(f.read) } + Kernel.open(url, headers) { |f| yield Utils::JSON.load(f.read) } rescue OpenURI::HTTPError => e handle_api_error(e) rescue EOFError, SocketError, OpenSSL::SSL::SSLError => e @@ -578,6 +620,8 @@ module GitHub raise RateLimitExceededError.new(reset, error) end + GitHub.api_credentials_error_message(e.io.meta) + case e.io.status.first when "401", "403" raise AuthenticationFailedError.new(e.message)