diff options
Diffstat (limited to 'lib/rubygems/gemcutter_utilities.rb')
-rw-r--r-- | lib/rubygems/gemcutter_utilities.rb | 104 |
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 |