utils, gist-logs: improve/fix credential handling.

The API used (`Net::HTTP::Post`) does not handle basic authentication
credentials in the same way as `open` so fix both cases so they work.

Also, do some general usability tweaks to point out to people what could
be wrong with their tokens or credentials to help them debug.

Closes Homebrew/homebrew#50410.

Signed-off-by: Mike McQuaid <mike@mikemcquaid.com>
This commit is contained in:
Mike McQuaid 2016-03-25 18:35:06 +08:00 committed by Xu Cheng
parent ca2abb2be6
commit 6135da800e
2 changed files with 77 additions and 20 deletions

View File

@ -37,12 +37,12 @@ module Homebrew
if ARGV.include?("--new-issue") || ARGV.switch?("n") if ARGV.include?("--new-issue") || ARGV.switch?("n")
auth = :AUTH_TOKEN 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 "You can create a personal access token: https://github.com/settings/tokens"
puts "and then set HOMEBREW_GITHUB_API_TOKEN as authentication method." puts "and then set HOMEBREW_GITHUB_API_TOKEN as authentication method."
puts puts
auth = :AUTH_BASIC auth = :AUTH_USER_LOGIN
end end
url = new_issue(f.tap, "#{f.name} failed to build on #{MacOS.full_version}", url, auth) 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 = GitHub.api_headers
headers["Content-Type"] = "application/json" headers["Content-Type"] = "application/json"
request = Net::HTTP::Post.new(path, headers) 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
login(request) if auth == :AUTH_BASIC request = Net::HTTP::Post.new(path, headers)
request.basic_auth(*basic_auth_credentials) if basic_auth_credentials
login(request) if auth == :AUTH_USER_LOGIN
request.body = Utils::JSON.dump(data) request.body = Utils::JSON.dump(data)
request request
@ -133,6 +145,7 @@ module Homebrew
when Net::HTTPCreated when Net::HTTPCreated
Utils::JSON.load get_body(response) Utils::JSON.load get_body(response)
else else
GitHub.api_credentials_error_message(response)
raise "HTTP #{response.code} #{response.message} (expected 201)" raise "HTTP #{response.code} #{response.message} (expected 201)"
end end
end end

View File

@ -484,7 +484,7 @@ module GitHub
class RateLimitExceededError < Error class RateLimitExceededError < Error
def initialize(reset, error) def initialize(reset, error)
super <<-EOS.undent super <<-EOS.undent
GitHub #{error} GitHub API Error: #{error}
Try again in #{pretty_ratelimit_reset(reset)}, or create a personal access token: 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} #{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" and then set the token as: export HOMEBREW_GITHUB_API_TOKEN="your_new_token"
@ -506,9 +506,12 @@ module GitHub
EOS EOS
else else
message << <<-EOS.undent 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: Clear them with:
printf "protocol=https\\nhost=github.com\\n" | git credential-osxkeychain erase 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 EOS
end end
super message super message
@ -536,32 +539,71 @@ module GitHub
end end
end end
def api_headers def api_credentials_type
@api_headers ||= begin token, username = api_credentials
headers = { if token && !token.empty?
"User-Agent" => HOMEBREW_USER_AGENT, if username && !username.empty?
"Accept" => "application/vnd.github.v3+json" :keychain
} else
token, username = api_credentials :environment
if token && !token.empty? end
if username && !username.empty? else
headers[:http_basic_authentication] = [username, token] :none
else end
headers["Authorization"] = "token #{token}" 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
end end
headers true
end end
end end
def api_headers
{
"User-Agent" => HOMEBREW_USER_AGENT,
"Accept" => "application/vnd.github.v3+json"
}
end
def open(url, &_block) def open(url, &_block)
# This is a no-op if the user is opting out of using the GitHub API. # This is a no-op if the user is opting out of using the GitHub API.
return if ENV["HOMEBREW_NO_GITHUB_API"] return if ENV["HOMEBREW_NO_GITHUB_API"]
require "net/https" 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 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 rescue OpenURI::HTTPError => e
handle_api_error(e) handle_api_error(e)
rescue EOFError, SocketError, OpenSSL::SSL::SSLError => e rescue EOFError, SocketError, OpenSSL::SSL::SSLError => e
@ -578,6 +620,8 @@ module GitHub
raise RateLimitExceededError.new(reset, error) raise RateLimitExceededError.new(reset, error)
end end
GitHub.api_credentials_error_message(e.io.meta)
case e.io.status.first case e.io.status.first
when "401", "403" when "401", "403"
raise AuthenticationFailedError.new(e.message) raise AuthenticationFailedError.new(e.message)