[前][次][番号順一覧][スレッド一覧]

ruby-changes:53899

From: hsbt <ko1@a...>
Date: Sat, 1 Dec 2018 20:01:05 +0900 (JST)
Subject: [ruby-changes:53899] hsbt:r66118 (trunk): Merge rubygems-3.0.0.beta3.

hsbt	2018-12-01 20:01:00 +0900 (Sat, 01 Dec 2018)

  New Revision: 66118

  https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=66118

  Log:
    Merge rubygems-3.0.0.beta3.
    
      * [GSoC] Multi-factor feature for RubyGems https://github.com/rubygems/rubygems/pull/2369

  Modified files:
    trunk/lib/rubygems/commands/owner_command.rb
    trunk/lib/rubygems/commands/push_command.rb
    trunk/lib/rubygems/commands/signin_command.rb
    trunk/lib/rubygems/gemcutter_utilities.rb
    trunk/lib/rubygems/test_utilities.rb
    trunk/lib/rubygems.rb
    trunk/test/rubygems/test_gem_commands_owner_command.rb
    trunk/test/rubygems/test_gem_commands_push_command.rb
    trunk/test/rubygems/test_gem_gemcutter_utilities.rb
Index: lib/rubygems.rb
===================================================================
--- lib/rubygems.rb	(revision 66117)
+++ lib/rubygems.rb	(revision 66118)
@@ -9,7 +9,7 @@ https://github.com/ruby/ruby/blob/trunk/lib/rubygems.rb#L9
 require 'rbconfig'
 
 module Gem
-  VERSION = "3.0.0.beta2".freeze
+  VERSION = "3.0.0.beta3".freeze
 end
 
 # Must be first since it unloads the prelude from 1.9.2
Index: lib/rubygems/test_utilities.rb
===================================================================
--- lib/rubygems/test_utilities.rb	(revision 66117)
+++ lib/rubygems/test_utilities.rb	(revision 66118)
@@ -87,7 +87,7 @@ class Gem::FakeFetcher https://github.com/ruby/ruby/blob/trunk/lib/rubygems/test_utilities.rb#L87
 
   def request(uri, request_class, last_modified = nil)
     data = find_data(uri)
-    body, code, msg = data
+    body, code, msg = (data.respond_to?(:call) ? data.call : data)
 
     @last_request = request_class.new uri.request_uri
     yield @last_request if block_given?
Index: lib/rubygems/gemcutter_utilities.rb
===================================================================
--- lib/rubygems/gemcutter_utilities.rb	(revision 66117)
+++ lib/rubygems/gemcutter_utilities.rb	(revision 66118)
@@ -25,6 +25,16 @@ module Gem::GemcutterUtilities https://github.com/ruby/ruby/blob/trunk/lib/rubygems/gemcutter_utilities.rb#L25
   end
 
   ##
+  # Add the --otp option
+
+  def add_otp_option
+    add_option('--otp CODE',
+               'Digit code for multifactor authentication') do |value, options|
+      options[:otp] = value
+    end
+  end
+
+  ##
   # The API key from the command options or from the user's configuration.
 
   def api_key
@@ -113,6 +123,13 @@ module Gem::GemcutterUtilities https://github.com/ruby/ruby/blob/trunk/lib/rubygems/gemcutter_utilities.rb#L123
       request.basic_auth email, password
     end
 
+    if need_otp? response
+      response = rubygems_api_request(:get, "api/v1/api_key", sign_in_host) do |request|
+        request.basic_auth email, password
+        request.add_field "OTP", options[:otp]
+      end
+    end
+
     with_response response do |resp|
       say "Signed in."
       set_api_key host, resp.body
@@ -156,6 +173,20 @@ module Gem::GemcutterUtilities https://github.com/ruby/ruby/blob/trunk/lib/rubygems/gemcutter_utilities.rb#L173
     end
   end
 
+  ##
+  # Returns true when the user has enabled multifactor authentication from
+  # +response+ text.
+
+  def need_otp?(response)
+    return unless response.kind_of?(Net::HTTPUnauthorized) &&
+        response.body.start_with?('You have enabled multifactor authentication')
+    return true if options[:otp]
+
+    say 'You have enabled multi-factor authentication. Please enter OTP code.'
+    options[:otp] = ask 'Code: '
+    true
+  end
+
   def set_api_key(host, key)
     if host == Gem::DEFAULT_HOST
       Gem.configuration.rubygems_api_key = key
Index: lib/rubygems/commands/push_command.rb
===================================================================
--- lib/rubygems/commands/push_command.rb	(revision 66117)
+++ lib/rubygems/commands/push_command.rb	(revision 66118)
@@ -33,6 +33,7 @@ command.  For further discussion see the https://github.com/ruby/ruby/blob/trunk/lib/rubygems/commands/push_command.rb#L33
 
     add_proxy_option
     add_key_option
+    add_otp_option
 
     add_option('--host HOST',
                'Push to another gemcutter-compatible host',
@@ -113,11 +114,10 @@ You can upgrade or downgrade to the late https://github.com/ruby/ruby/blob/trunk/lib/rubygems/commands/push_command.rb#L114
 
     say "Pushing gem to #{@host || Gem.host}..."
 
-    response = rubygems_api_request(*args) do |request|
-      request.body = Gem.read_binary name
-      request.add_field "Content-Length", request.body.size
-      request.add_field "Content-Type",   "application/octet-stream"
-      request.add_field "Authorization",  api_key
+    response = send_push_request(name, args)
+
+    if need_otp? response
+      response = send_push_request(name, args, true)
     end
 
     with_response response
@@ -125,6 +125,16 @@ You can upgrade or downgrade to the late https://github.com/ruby/ruby/blob/trunk/lib/rubygems/commands/push_command.rb#L125
 
   private
 
+  def send_push_request(name, args, use_otp = false)
+    rubygems_api_request(*args) do |request|
+      request.body = Gem.read_binary name
+      request.add_field "Content-Length", request.body.size
+      request.add_field "Content-Type",   "application/octet-stream"
+      request.add_field "Authorization",  api_key
+      request.add_field "OTP", options[:otp] if use_otp
+    end
+  end
+
   def get_hosts_for(name)
     gem_metadata = Gem::Package.new(name).spec.metadata
 
Index: lib/rubygems/commands/signin_command.rb
===================================================================
--- lib/rubygems/commands/signin_command.rb	(revision 66117)
+++ lib/rubygems/commands/signin_command.rb	(revision 66118)
@@ -12,6 +12,8 @@ class Gem::Commands::SigninCommand < Gem https://github.com/ruby/ruby/blob/trunk/lib/rubygems/commands/signin_command.rb#L12
     add_option('--host HOST', 'Push to another gemcutter-compatible host') do |value, options|
       options[:host] = value
     end
+
+    add_otp_option
   end
 
   def description # :nodoc:
Index: lib/rubygems/commands/owner_command.rb
===================================================================
--- lib/rubygems/commands/owner_command.rb	(revision 66117)
+++ lib/rubygems/commands/owner_command.rb	(revision 66118)
@@ -30,6 +30,7 @@ permission to. https://github.com/ruby/ruby/blob/trunk/lib/rubygems/commands/owner_command.rb#L30
     super 'owner', 'Manage gem owners of a gem on the push server'
     add_proxy_option
     add_key_option
+    add_otp_option
     defaults.merge! :add => [], :remove => []
 
     add_option '-a', '--add EMAIL', 'Add an owner' do |value, options|
@@ -84,9 +85,10 @@ permission to. https://github.com/ruby/ruby/blob/trunk/lib/rubygems/commands/owner_command.rb#L85
   def manage_owners(method, name, owners)
     owners.each do |owner|
       begin
-        response = rubygems_api_request method, "api/v1/gems/#{name}/owners" do |request|
-          request.set_form_data 'email' => owner
-          request.add_field "Authorization", api_key
+        response = send_owner_request(method, name, owner)
+
+        if need_otp? response
+          response = send_owner_request(method, name, owner, true)
         end
 
         action = method == :delete ? "Removing" : "Adding"
@@ -98,4 +100,14 @@ permission to. https://github.com/ruby/ruby/blob/trunk/lib/rubygems/commands/owner_command.rb#L100
     end
   end
 
+  private
+
+  def send_owner_request(method, name, owner, use_otp = false)
+    rubygems_api_request method, "api/v1/gems/#{name}/owners" do |request|
+      request.set_form_data 'email' => owner
+      request.add_field "Authorization", api_key
+      request.add_field "OTP", options[:otp] if use_otp
+    end
+  end
+
 end
Index: test/rubygems/test_gem_commands_push_command.rb
===================================================================
--- test/rubygems/test_gem_commands_push_command.rb	(revision 66117)
+++ test/rubygems/test_gem_commands_push_command.rb	(revision 66118)
@@ -161,6 +161,7 @@ class TestGemCommandsPushCommand < Gem:: https://github.com/ruby/ruby/blob/trunk/test/rubygems/test_gem_commands_push_command.rb#L161
 
     @response = "Successfully registered gem: freebird (1.0.1)"
     @fetcher.data["#{@host}/api/v1/gems"]  = [@response, 200, 'OK']
+
     send_battery
   end
 
@@ -230,6 +231,7 @@ class TestGemCommandsPushCommand < Gem:: https://github.com/ruby/ruby/blob/trunk/test/rubygems/test_gem_commands_push_command.rb#L231
       spec.metadata['allowed_push_host'] = "https://privategemserver.example"
     end
 
+
     response = %{ERROR:  "#{@host}" is not allowed by the gemspec, which only allows "https://privategemserver.example"}
 
     assert_raises Gem::MockGemUi::TermError do
@@ -347,4 +349,41 @@ class TestGemCommandsPushCommand < Gem:: https://github.com/ruby/ruby/blob/trunk/test/rubygems/test_gem_commands_push_command.rb#L349
                  @fetcher.last_request["Authorization"]
   end
 
+  def test_otp_verified_success
+    response_fail = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry."
+    response_success = 'Successfully registered gem: freewill (1.0.0)'
+
+    @fetcher.data["#{Gem.host}/api/v1/gems"] = proc do
+      @call_count ||= 0
+      (@call_count += 1).odd? ? [response_fail, 401, 'Unauthorized'] : [response_success, 200, 'OK']
+    end
+
+    @otp_ui = Gem::MockGemUi.new "111111\n"
+    use_ui @otp_ui do
+      @cmd.send_gem(@path)
+    end
+
+    assert_match 'You have enabled multi-factor authentication. Please enter OTP code.', @otp_ui.output
+    assert_match 'Code: ', @otp_ui.output
+    assert_match response_success, @otp_ui.output
+    assert_equal '111111', @fetcher.last_request['OTP']
+  end
+
+  def test_otp_verified_failure
+    response = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry."
+    @fetcher.data["#{Gem.host}/api/v1/gems"] = [response, 401, 'Unauthorized']
+
+    @otp_ui = Gem::MockGemUi.new "111111\n"
+    assert_raises Gem::MockGemUi::TermError do
+      use_ui @otp_ui do
+        @cmd.send_gem(@path)
+      end
+    end
+
+    assert_match response, @otp_ui.output
+    assert_match 'You have enabled multi-factor authentication. Please enter OTP code.', @otp_ui.output
+    assert_match 'Code: ', @otp_ui.output
+    assert_equal '111111', @fetcher.last_request['OTP']
+  end
+
 end
Index: test/rubygems/test_gem_gemcutter_utilities.rb
===================================================================
--- test/rubygems/test_gem_gemcutter_utilities.rb	(revision 66117)
+++ test/rubygems/test_gem_gemcutter_utilities.rb	(revision 66118)
@@ -187,7 +187,35 @@ class TestGemGemcutterUtilities < Gem::T https://github.com/ruby/ruby/blob/trunk/test/rubygems/test_gem_gemcutter_utilities.rb#L187
     assert_match %r{Access Denied.}, @sign_in_ui.output
   end
 
-  def util_sign_in(response, host = nil, args = [])
+  def test_sign_in_with_correct_otp_code
+    api_key       = 'a5fdbb6ba150cbb83aad2bb2fede64cf040453903'
+    response_fail = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry."
+
+    util_sign_in(proc do
+      @call_count ||= 0
+      (@call_count += 1).odd? ? [response_fail, 401, 'Unauthorized'] : [api_key, 200, 'OK']
+    end, nil, [], "111111\n")
+
+    assert_match 'You have enabled multi-factor authentication. Please enter OTP code.', @sign_in_ui.output
+    assert_match 'Code: ', @sign_in_ui.output
+    assert_match 'Signed in.', @sign_in_ui.output
+    assert_equal '111111', @fetcher.last_request['OTP']
+  end
+
+  def test_sign_in_with_incorrect_otp_code
+    response = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry."
+
+    assert_raises Gem::MockGemUi::TermError do
+      util_sign_in [response, 401, 'Unauthorized'], nil, [], "111111\n"
+    end
+
+    assert_match 'You have enabled multi-factor authentication. Please enter OTP code.', @sign_in_ui.output
+    assert_match 'Code: ', @sign_in_ui.output
+    assert_match response, @sign_in_ui.output
+    assert_equal '111111', @fetcher.last_request['OTP']
+  end
+
+  def util_sign_in(response, host = nil, args = [], extra_input = '')
     email    = 'you@e...'
     password = 'secret'
 
@@ -201,7 +229,7 @@ class TestGemGemcutterUtilities < Gem::T https://github.com/ruby/ruby/blob/trunk/test/rubygems/test_gem_gemcutter_utilities.rb#L229
     @fetcher.data["#{host}/api/v1/api_key"] = response
     Gem::RemoteFetcher.fetcher = @fetcher
 
-    @sign_in_ui = Gem::MockGemUi.new "#{email}\n#{password}\n"
+    @sign_in_ui = Gem::MockGemUi.new("#{email}\n#{password}\n" + extra_input)
 
     use_ui @sign_in_ui do
       if args.length > 0
Index: test/rubygems/test_gem_commands_owner_command.rb
===================================================================
--- test/rubygems/test_gem_commands_owner_command.rb	(revision 66117)
+++ test/rubygems/test_gem_commands_owner_command.rb	(revision 66118)
@@ -235,4 +235,39 @@ EOF https://github.com/ruby/ruby/blob/trunk/test/rubygems/test_gem_commands_owner_command.rb#L235
     assert_equal "Removing missing@example: #{response}\n", @stub_ui.output
   end
 
+  def test_otp_verified_success
+    response_fail = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry."
+    response_success = "Owner added successfully."
+
+    @stub_fetcher.data["#{Gem.host}/api/v1/gems/freewill/owners"] = proc do
+      @call_count ||= 0
+      (@call_count += 1).odd? ? [response_fail, 401, 'Unauthorized'] : [response_success, 200, 'OK']
+    end
+
+    @otp_ui = Gem::MockGemUi.new "111111\n"
+    use_ui @otp_ui do
+      @cmd.add_owners("freewill", ["user-new1@e..."])
+    end
+
+    assert_match 'You have enabled multi-factor authentication. Please enter OTP code.', @otp_ui.output
+    assert_match 'Code: ', @otp_ui.output
+    assert_match response_success, @otp_ui.output
+    assert_equal '111111', @stub_fetcher.last_request['OTP']
+  end
+
+  def test_otp_verified_failure
+    response = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry."
+    @stub_fetcher.data["#{Gem.host}/api/v1/gems/freewill/owners"] = [response, 401, 'Unauthorized']
+
+    @otp_ui = Gem::MockGemUi.new "111111\n"
+    use_ui @otp_ui do
+      @cmd.add_owners("freewill", ["user-new1@e..."])
+    end
+
+    assert_match response, @otp_ui.output
+    assert_match 'You have enabled multi-factor authentication. Please enter OTP code.', @otp_ui.output
+    assert_match 'Code: ', @otp_ui.output
+    assert_equal '111111', @stub_fetcher.last_request['OTP']
+  end
+
 end

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]