Skip to content

Commit

Permalink
utils, gist-logs: improve/fix credential handling.
Browse files Browse the repository at this point in the history
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/legacy-homebrew#50410.

Signed-off-by: Mike McQuaid <mike@mikemcquaid.com>
  • Loading branch information
MikeMcQuaid authored and xu-cheng committed Mar 28, 2016
1 parent ca2abb2 commit 6135da8
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 19 deletions.
19 changes: 16 additions & 3 deletions Library/Homebrew/cmd/gist-logs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ def gistify_logs(f)
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)
Expand Down Expand Up @@ -118,9 +118,21 @@ def make_request(path, data, auth)
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
Expand All @@ -133,6 +145,7 @@ def post(path, data, auth = nil)
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
Expand Down
76 changes: 60 additions & 16 deletions Library/Homebrew/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -506,9 +506,12 @@ def initialize(error)
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
Expand Down Expand Up @@ -536,32 +539,71 @@ def api_credentials
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
Expand All @@ -578,6 +620,8 @@ def handle_api_error(e)
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)
Expand Down

0 comments on commit 6135da8

Please sign in to comment.