summaryrefslogtreecommitdiff
path: root/lib/rubygems/gemcutter_utilities.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rubygems/gemcutter_utilities.rb')
-rw-r--r--lib/rubygems/gemcutter_utilities.rb104
1 files changed, 90 insertions, 14 deletions
diff --git a/lib/rubygems/gemcutter_utilities.rb b/lib/rubygems/gemcutter_utilities.rb
index bba9280691..d021f47e24 100644
--- a/lib/rubygems/gemcutter_utilities.rb
+++ b/lib/rubygems/gemcutter_utilities.rb
@@ -8,10 +8,12 @@ require 'rubygems/text'
module Gem::GemcutterUtilities
ERROR_CODE = 1
+ API_SCOPES = %i[index_rubygems push_rubygem yank_rubygem add_owner remove_owner access_webhooks show_dashboard].freeze
include Gem::Text
attr_writer :host
+ attr_writer :scope
##
# Add the --key option
@@ -72,7 +74,7 @@ module Gem::GemcutterUtilities
#
# If +allowed_push_host+ metadata is present, then it will only allow that host.
- def rubygems_api_request(method, path, host = nil, allowed_push_host = nil, &block)
+ def rubygems_api_request(method, path, host = nil, allowed_push_host = nil, scope: nil, &block)
require 'net/http'
self.host = host if host
@@ -95,11 +97,19 @@ module Gem::GemcutterUtilities
request_method = Net::HTTP.const_get method.to_s.capitalize
response = Gem::RemoteFetcher.fetcher.request(uri, request_method, &block)
- return response unless mfa_unauthorized?(response)
- Gem::RemoteFetcher.fetcher.request(uri, request_method) do |req|
- req.add_field "OTP", get_otp
- block.call(req)
+ if mfa_unauthorized?(response)
+ response = Gem::RemoteFetcher.fetcher.request(uri, request_method) do |req|
+ req.add_field "OTP", get_otp
+ block.call(req)
+ end
+ end
+
+ if api_key_forbidden?(response)
+ update_scope(scope)
+ Gem::RemoteFetcher.fetcher.request(uri, request_method, &block)
+ else
+ response
end
end
@@ -112,19 +122,37 @@ module Gem::GemcutterUtilities
ask 'Code: '
end
+ def update_scope(scope)
+ sign_in_host = self.host
+ pretty_host = pretty_host(sign_in_host)
+ update_scope_params = { scope => true }
+
+ say "The existing key doesn't have access of #{scope} on #{pretty_host}. Please sign in to update access."
+
+ email = ask " Email: "
+ password = ask_for_password "Password: "
+
+ response = rubygems_api_request(:put, "api/v1/api_key",
+ sign_in_host, scope: scope) do |request|
+ request.basic_auth email, password
+ request.add_field "OTP", options[:otp] if options[:otp]
+ request.body = URI.encode_www_form({:api_key => api_key }.merge(update_scope_params))
+ end
+
+ with_response response do |resp|
+ say "Added #{scope} scope to the existing API key"
+ end
+ end
+
##
# Signs in with the RubyGems API at +sign_in_host+ and sets the rubygems API
# key.
- def sign_in(sign_in_host = nil)
+ def sign_in(sign_in_host = nil, scope: nil)
sign_in_host ||= self.host
return if api_key
- pretty_host = if Gem::DEFAULT_HOST == sign_in_host
- 'RubyGems.org'
- else
- sign_in_host
- end
+ pretty_host = pretty_host(sign_in_host)
say "Enter your #{pretty_host} credentials."
say "Don't have an account yet? " +
@@ -134,14 +162,18 @@ module Gem::GemcutterUtilities
password = ask_for_password "Password: "
say "\n"
- response = rubygems_api_request(:get, "api/v1/api_key",
- sign_in_host) do |request|
+ key_name = get_key_name(scope)
+ scope_params = get_scope_params(scope)
+
+ response = rubygems_api_request(:post, "api/v1/api_key",
+ sign_in_host, scope: scope) do |request|
request.basic_auth email, password
request.add_field "OTP", options[:otp] if options[:otp]
+ request.body = URI.encode_www_form({ name: key_name }.merge(scope_params))
end
with_response response do |resp|
- say "Signed in."
+ say "Signed in with API key: #{key_name}."
set_api_key host, resp.body
end
end
@@ -195,4 +227,48 @@ module Gem::GemcutterUtilities
end
end
+ private
+
+ def pretty_host(host)
+ if Gem::DEFAULT_HOST == host
+ 'RubyGems.org'
+ else
+ host
+ end
+ end
+
+ def get_scope_params(scope)
+ scope_params = {}
+
+ if scope
+ scope_params = { scope => true }
+ else
+ say "Please select scopes you want to enable for the API key (y/n)"
+ API_SCOPES.each do |scope|
+ selected = ask "#{scope} [y/N]: "
+ scope_params[scope] = true if selected =~ /^[yY](es)?$/
+ end
+ say "\n"
+ end
+
+ scope_params
+ end
+
+ def get_key_name(scope)
+ hostname = Socket.gethostname || "unkown-host"
+ user = ENV["USER"] || ENV["USERNAME"] || "unkown-user"
+ ts = Time.now.strftime("%Y%m%d%H%M%S")
+ default_key_name = "#{hostname}-#{user}-#{ts}"
+
+ key_name = ask "API Key name [#{default_key_name}]: " unless scope
+ if key_name.nil? || key_name.empty?
+ default_key_name
+ else
+ key_name
+ end
+ end
+
+ def api_key_forbidden?(response)
+ response.kind_of?(Net::HTTPForbidden) && response.body.start_with?("The API key doesn't have access")
+ end
end