aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMike McQuaid2016-03-25 18:35:06 +0800
committerXu Cheng2016-03-28 18:22:47 +0800
commit6135da800e4f93ded3a5a331d0efa691f4822afb (patch)
treeba02645a64a9a866bb77bcb586f07addee060c7c
parentca2abb2be6686a8f2e7c64f5d334ab70be97a1b7 (diff)
downloadbrew-6135da800e4f93ded3a5a331d0efa691f4822afb.tar.bz2
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>
-rw-r--r--Library/Homebrew/cmd/gist-logs.rb19
-rw-r--r--Library/Homebrew/utils.rb76
2 files changed, 76 insertions, 19 deletions
diff --git a/Library/Homebrew/cmd/gist-logs.rb b/Library/Homebrew/cmd/gist-logs.rb
index acc9a10ea..5954c031e 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 1fbb5afac..98ebcc9c8 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)