rubygems-update 3.4.10 → 3.4.14

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -0
  3. data/Manifest.txt +5 -0
  4. data/bundler/CHANGELOG.md +48 -0
  5. data/bundler/exe/bundle +5 -13
  6. data/bundler/lib/bundler/build_metadata.rb +2 -2
  7. data/bundler/lib/bundler/definition.rb +14 -6
  8. data/bundler/lib/bundler/gem_version_promoter.rb +1 -1
  9. data/bundler/lib/bundler/installer.rb +1 -1
  10. data/bundler/lib/bundler/lazy_specification.rb +1 -1
  11. data/bundler/lib/bundler/lockfile_parser.rb +1 -0
  12. data/bundler/lib/bundler/man/bundle-cache.1 +2 -2
  13. data/bundler/lib/bundler/man/bundle-cache.1.ronn +2 -2
  14. data/bundler/lib/bundler/resolver/base.rb +1 -3
  15. data/bundler/lib/bundler/ruby_version.rb +1 -1
  16. data/bundler/lib/bundler/rubygems_ext.rb +5 -3
  17. data/bundler/lib/bundler/safe_marshal.rb +31 -0
  18. data/bundler/lib/bundler/settings.rb +3 -2
  19. data/bundler/lib/bundler/source/rubygems.rb +12 -12
  20. data/bundler/lib/bundler/spec_set.rb +2 -2
  21. data/bundler/lib/bundler/templates/newgem/bin/console.tt +0 -4
  22. data/bundler/lib/bundler/templates/newgem/ext/newgem/extconf-c.rb.tt +5 -0
  23. data/bundler/lib/bundler/templates/newgem/ext/newgem/newgem.c.tt +1 -1
  24. data/bundler/lib/bundler/templates/newgem/newgem.gemspec.tt +2 -1
  25. data/bundler/lib/bundler/vendor/pub_grub/lib/pub_grub/version_solver.rb +9 -4
  26. data/bundler/lib/bundler/vendor/uri/lib/uri/rfc3986_parser.rb +2 -2
  27. data/bundler/lib/bundler/vendor/uri/lib/uri/version.rb +1 -1
  28. data/bundler/lib/bundler/version.rb +1 -1
  29. data/bundler/lib/bundler.rb +8 -16
  30. data/lib/rubygems/command_manager.rb +2 -2
  31. data/lib/rubygems/commands/owner_command.rb +4 -2
  32. data/lib/rubygems/exceptions.rb +10 -0
  33. data/lib/rubygems/gemcutter_utilities.rb +48 -6
  34. data/lib/rubygems/installer.rb +16 -1
  35. data/lib/rubygems/request_set.rb +2 -2
  36. data/lib/rubygems/specification.rb +3 -1
  37. data/lib/rubygems/stub_specification.rb +2 -1
  38. data/lib/rubygems/webauthn_listener/response.rb +161 -0
  39. data/lib/rubygems/webauthn_listener.rb +92 -0
  40. data/lib/rubygems.rb +1 -1
  41. data/rubygems-update.gemspec +4 -3
  42. data/test/rubygems/helper.rb +14 -0
  43. data/test/rubygems/test_bundled_ca.rb +1 -1
  44. data/test/rubygems/test_config.rb +1 -1
  45. data/test/rubygems/test_deprecate.rb +1 -1
  46. data/test/rubygems/test_exit.rb +1 -1
  47. data/test/rubygems/test_gem.rb +7 -0
  48. data/test/rubygems/test_gem_commands_owner_command.rb +67 -0
  49. data/test/rubygems/test_gem_commands_pristine_command.rb +1 -1
  50. data/test/rubygems/test_gem_commands_push_command.rb +73 -0
  51. data/test/rubygems/test_gem_commands_setup_command.rb +1 -1
  52. data/test/rubygems/test_gem_commands_yank_command.rb +84 -0
  53. data/test/rubygems/test_gem_ext_cargo_builder.rb +1 -0
  54. data/test/rubygems/test_gem_gem_runner.rb +5 -5
  55. data/test/rubygems/test_gem_gemcutter_utilities.rb +72 -4
  56. data/test/rubygems/test_gem_installer.rb +50 -2
  57. data/test/rubygems/test_gem_uninstaller.rb +4 -4
  58. data/test/rubygems/test_kernel.rb +1 -1
  59. data/test/rubygems/test_project_sanity.rb +32 -3
  60. data/test/rubygems/test_remote_fetch_error.rb +1 -1
  61. data/test/rubygems/test_webauthn_listener.rb +120 -0
  62. data/test/rubygems/test_webauthn_listener_response.rb +93 -0
  63. data/test/rubygems/utilities.rb +43 -3
  64. metadata +13 -6
@@ -330,6 +330,8 @@ EOF
330
330
  HTTPResponseFactory.create(body: response_fail, code: 401, msg: "Unauthorized"),
331
331
  HTTPResponseFactory.create(body: response_success, code: 200, msg: "OK"),
332
332
  ]
333
+ @stub_fetcher.data["#{Gem.host}/api/v1/webauthn_verification"] =
334
+ HTTPResponseFactory.create(body: "You don't have any security devices", code: 422, msg: "Unprocessable Entity")
333
335
 
334
336
  @otp_ui = Gem::MockGemUi.new "111111\n"
335
337
  use_ui @otp_ui do
@@ -345,6 +347,8 @@ EOF
345
347
  def test_otp_verified_failure
346
348
  response = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry."
347
349
  @stub_fetcher.data["#{Gem.host}/api/v1/gems/freewill/owners"] = HTTPResponseFactory.create(body: response, code: 401, msg: "Unauthorized")
350
+ @stub_fetcher.data["#{Gem.host}/api/v1/webauthn_verification"] =
351
+ HTTPResponseFactory.create(body: "You don't have any security devices", code: 422, msg: "Unprocessable Entity")
348
352
 
349
353
  @otp_ui = Gem::MockGemUi.new "111111\n"
350
354
  use_ui @otp_ui do
@@ -357,6 +361,69 @@ EOF
357
361
  assert_equal "111111", @stub_fetcher.last_request["OTP"]
358
362
  end
359
363
 
364
+ def test_with_webauthn_enabled_success
365
+ webauthn_verification_url = "rubygems.org/api/v1/webauthn_verification/odow34b93t6aPCdY"
366
+ response_fail = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry."
367
+ response_success = "Owner added successfully."
368
+ port = 5678
369
+ server = TCPServer.new(port)
370
+
371
+ @stub_fetcher.data["#{Gem.host}/api/v1/webauthn_verification"] = HTTPResponseFactory.create(body: webauthn_verification_url, code: 200, msg: "OK")
372
+ @stub_fetcher.data["#{Gem.host}/api/v1/gems/freewill/owners"] = [
373
+ HTTPResponseFactory.create(body: response_fail, code: 401, msg: "Unauthorized"),
374
+ HTTPResponseFactory.create(body: response_success, code: 200, msg: "OK"),
375
+ ]
376
+
377
+ TCPServer.stub(:new, server) do
378
+ Gem::WebauthnListener.stub(:wait_for_otp_code, "Uvh6T57tkWuUnWYo") do
379
+ use_ui @stub_ui do
380
+ @cmd.add_owners("freewill", ["user-new1@example.com"])
381
+ end
382
+ end
383
+ ensure
384
+ server.close
385
+ end
386
+
387
+ url_with_port = "#{webauthn_verification_url}?port=#{port}"
388
+ assert_match "You have enabled multi-factor authentication. Please visit #{url_with_port} to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin command with the `--otp [your_code]` option.", @stub_ui.output
389
+ assert_match "You are verified with a security device. You may close the browser window.", @stub_ui.output
390
+ assert_equal "Uvh6T57tkWuUnWYo", @stub_fetcher.last_request["OTP"]
391
+ assert_match response_success, @stub_ui.output
392
+ end
393
+
394
+ def test_with_webauthn_enabled_failure
395
+ webauthn_verification_url = "rubygems.org/api/v1/webauthn_verification/odow34b93t6aPCdY"
396
+ response_fail = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry."
397
+ response_success = "Owner added successfully."
398
+ port = 5678
399
+ server = TCPServer.new(port)
400
+ raise_error = ->(*_args) { raise Gem::WebauthnVerificationError, "Something went wrong" }
401
+
402
+ @stub_fetcher.data["#{Gem.host}/api/v1/webauthn_verification"] = HTTPResponseFactory.create(body: webauthn_verification_url, code: 200, msg: "OK")
403
+ @stub_fetcher.data["#{Gem.host}/api/v1/gems/freewill/owners"] = [
404
+ HTTPResponseFactory.create(body: response_fail, code: 401, msg: "Unauthorized"),
405
+ HTTPResponseFactory.create(body: response_success, code: 200, msg: "OK"),
406
+ ]
407
+
408
+ TCPServer.stub(:new, server) do
409
+ Gem::WebauthnListener.stub(:wait_for_otp_code, raise_error) do
410
+ use_ui @stub_ui do
411
+ @cmd.add_owners("freewill", ["user-new1@example.com"])
412
+ end
413
+ end
414
+ ensure
415
+ server.close
416
+ end
417
+
418
+ url_with_port = "#{webauthn_verification_url}?port=#{port}"
419
+
420
+ assert_match @stub_fetcher.last_request["Authorization"], Gem.configuration.rubygems_api_key
421
+ assert_match "You have enabled multi-factor authentication. Please visit #{url_with_port} to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin command with the `--otp [your_code]` option.", @stub_ui.output
422
+ assert_match "ERROR: Security device verification failed: Something went wrong", @stub_ui.error
423
+ refute_match "You are verified with a security device. You may close the browser window.", @stub_ui.output
424
+ refute_match response_success, @stub_ui.output
425
+ end
426
+
360
427
  def test_remove_owners_unathorized_api_key
361
428
  response_forbidden = "The API key doesn't have access"
362
429
  response_success = "Owner removed successfully."
@@ -545,7 +545,7 @@ class TestGemCommandsPristineCommand < Gem::TestCase
545
545
  fp.puts "puts __FILE__"
546
546
  end
547
547
  write_file File.join(@tempdir, "lib", "rubygems_plugin.rb") do |fp|
548
- fp.puts "puts __FILE__"
548
+ fp.puts "# do nothing"
549
549
  end
550
550
  write_file File.join(@tempdir, "bin", "foo") do |fp|
551
551
  fp.puts "#!/usr/bin/ruby"
@@ -391,6 +391,8 @@ class TestGemCommandsPushCommand < Gem::TestCase
391
391
  HTTPResponseFactory.create(body: response_fail, code: 401, msg: "Unauthorized"),
392
392
  HTTPResponseFactory.create(body: response_success, code: 200, msg: "OK"),
393
393
  ]
394
+ @fetcher.data["#{Gem.host}/api/v1/webauthn_verification"] =
395
+ HTTPResponseFactory.create(body: "You don't have any security devices", code: 422, msg: "Unprocessable Entity")
394
396
 
395
397
  @otp_ui = Gem::MockGemUi.new "111111\n"
396
398
  use_ui @otp_ui do
@@ -406,6 +408,8 @@ class TestGemCommandsPushCommand < Gem::TestCase
406
408
  def test_otp_verified_failure
407
409
  response = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry."
408
410
  @fetcher.data["#{Gem.host}/api/v1/gems"] = HTTPResponseFactory.create(body: response, code: 401, msg: "Unauthorized")
411
+ @fetcher.data["#{Gem.host}/api/v1/webauthn_verification"] =
412
+ HTTPResponseFactory.create(body: "You don't have any security devices", code: 422, msg: "Unprocessable Entity")
409
413
 
410
414
  @otp_ui = Gem::MockGemUi.new "111111\n"
411
415
  assert_raise Gem::MockGemUi::TermError do
@@ -420,6 +424,71 @@ class TestGemCommandsPushCommand < Gem::TestCase
420
424
  assert_equal "111111", @fetcher.last_request["OTP"]
421
425
  end
422
426
 
427
+ def test_with_webauthn_enabled_success
428
+ webauthn_verification_url = "rubygems.org/api/v1/webauthn_verification/odow34b93t6aPCdY"
429
+ response_fail = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry."
430
+ response_success = "Successfully registered gem: freewill (1.0.0)"
431
+ port = 5678
432
+ server = TCPServer.new(port)
433
+
434
+ @fetcher.data["#{Gem.host}/api/v1/gems"] = [
435
+ HTTPResponseFactory.create(body: response_fail, code: 401, msg: "Unauthorized"),
436
+ HTTPResponseFactory.create(body: response_success, code: 200, msg: "OK"),
437
+ ]
438
+ @fetcher.data["#{Gem.host}/api/v1/webauthn_verification"] = HTTPResponseFactory.create(body: webauthn_verification_url, code: 200, msg: "OK")
439
+
440
+ TCPServer.stub(:new, server) do
441
+ Gem::WebauthnListener.stub(:wait_for_otp_code, "Uvh6T57tkWuUnWYo") do
442
+ use_ui @ui do
443
+ @cmd.send_gem(@path)
444
+ end
445
+ end
446
+ ensure
447
+ server.close
448
+ end
449
+
450
+ url_with_port = "#{webauthn_verification_url}?port=#{port}"
451
+ assert_match "You have enabled multi-factor authentication. Please visit #{url_with_port} to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin command with the `--otp [your_code]` option.", @ui.output
452
+ assert_match "You are verified with a security device. You may close the browser window.", @ui.output
453
+ assert_equal "Uvh6T57tkWuUnWYo", @fetcher.last_request["OTP"]
454
+ assert_match response_success, @ui.output
455
+ end
456
+
457
+ def test_with_webauthn_enabled_failure
458
+ webauthn_verification_url = "rubygems.org/api/v1/webauthn_verification/odow34b93t6aPCdY"
459
+ response_fail = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry."
460
+ response_success = "Successfully registered gem: freewill (1.0.0)"
461
+ port = 5678
462
+ server = TCPServer.new(port)
463
+ raise_error = ->(*_args) { raise Gem::WebauthnVerificationError, "Something went wrong" }
464
+
465
+ @fetcher.data["#{Gem.host}/api/v1/gems"] = [
466
+ HTTPResponseFactory.create(body: response_fail, code: 401, msg: "Unauthorized"),
467
+ HTTPResponseFactory.create(body: response_success, code: 200, msg: "OK"),
468
+ ]
469
+ @fetcher.data["#{Gem.host}/api/v1/webauthn_verification"] = HTTPResponseFactory.create(body: webauthn_verification_url, code: 200, msg: "OK")
470
+
471
+ error = assert_raise Gem::MockGemUi::TermError do
472
+ TCPServer.stub(:new, server) do
473
+ Gem::WebauthnListener.stub(:wait_for_otp_code, raise_error) do
474
+ use_ui @ui do
475
+ @cmd.send_gem(@path)
476
+ end
477
+ end
478
+ ensure
479
+ server.close
480
+ end
481
+ end
482
+ assert_equal 1, error.exit_code
483
+
484
+ assert_match @fetcher.last_request["Authorization"], Gem.configuration.rubygems_api_key
485
+ url_with_port = "#{webauthn_verification_url}?port=#{port}"
486
+ assert_match "You have enabled multi-factor authentication. Please visit #{url_with_port} to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin command with the `--otp [your_code]` option.", @ui.output
487
+ assert_match "ERROR: Security device verification failed: Something went wrong", @ui.error
488
+ refute_match "You are verified with a security device. You may close the browser window.", @ui.output
489
+ refute_match response_success, @ui.output
490
+ end
491
+
423
492
  def test_sending_gem_unathorized_api_key_with_mfa_enabled
424
493
  response_mfa_enabled = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry."
425
494
  response_forbidden = "The API key doesn't have access"
@@ -430,6 +499,8 @@ class TestGemCommandsPushCommand < Gem::TestCase
430
499
  HTTPResponseFactory.create(body: response_forbidden, code: 403, msg: "Forbidden"),
431
500
  HTTPResponseFactory.create(body: response_success, code: 200, msg: "OK"),
432
501
  ]
502
+ @fetcher.data["#{@host}/api/v1/webauthn_verification"] =
503
+ HTTPResponseFactory.create(body: "You don't have any security devices", code: 422, msg: "Unprocessable Entity")
433
504
 
434
505
  @fetcher.data["#{@host}/api/v1/api_key"] = HTTPResponseFactory.create(body: "", code: 200, msg: "OK")
435
506
  @cmd.instance_variable_set :@host, @host
@@ -470,6 +541,8 @@ class TestGemCommandsPushCommand < Gem::TestCase
470
541
  @fetcher.data["#{@host}/api/v1/profile/me.yaml"] = [
471
542
  HTTPResponseFactory.create(body: response_profile, code: 200, msg: "OK"),
472
543
  ]
544
+ @fetcher.data["#{@host}/api/v1/webauthn_verification"] =
545
+ HTTPResponseFactory.create(body: "You don't have any security devices", code: 422, msg: "Unprocessable Entity")
473
546
 
474
547
  @cmd.instance_variable_set :@scope, :push_rubygem
475
548
  @cmd.options[:args] = [@path]
@@ -433,7 +433,7 @@ class TestGemCommandsSetupCommand < Gem::TestCase
433
433
  s.files = %W[lib/rubygems_plugin.rb]
434
434
  end
435
435
  write_file File.join @tempdir, "lib", "rubygems_plugin.rb" do |f|
436
- f.puts "require '#{gem.plugins.first}'"
436
+ f.puts "# do nothing"
437
437
  end
438
438
  install_gem gem
439
439
 
@@ -72,6 +72,9 @@ class TestGemCommandsYankCommand < Gem::TestCase
72
72
  HTTPResponseFactory.create(body: response_fail, code: 401, msg: "Unauthorized"),
73
73
  HTTPResponseFactory.create(body: "Successfully yanked", code: 200, msg: "OK"),
74
74
  ]
75
+ webauthn_uri = "http://example/api/v1/webauthn_verification"
76
+ @fetcher.data[webauthn_uri] =
77
+ HTTPResponseFactory.create(body: "You don't have any security devices", code: 422, msg: "Unprocessable Entity")
75
78
 
76
79
  @cmd.options[:args] = %w[a]
77
80
  @cmd.options[:added_platform] = true
@@ -93,6 +96,9 @@ class TestGemCommandsYankCommand < Gem::TestCase
93
96
  response = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry."
94
97
  yank_uri = "http://example/api/v1/gems/yank"
95
98
  @fetcher.data[yank_uri] = HTTPResponseFactory.create(body: response, code: 401, msg: "Unauthorized")
99
+ webauthn_uri = "http://example/api/v1/webauthn_verification"
100
+ @fetcher.data[webauthn_uri] =
101
+ HTTPResponseFactory.create(body: "You don't have any security devices", code: 422, msg: "Unprocessable Entity")
96
102
 
97
103
  @cmd.options[:args] = %w[a]
98
104
  @cmd.options[:added_platform] = true
@@ -109,6 +115,84 @@ class TestGemCommandsYankCommand < Gem::TestCase
109
115
  assert_equal "111111", @fetcher.last_request["OTP"]
110
116
  end
111
117
 
118
+ def test_with_webauthn_enabled_success
119
+ webauthn_verification_url = "http://example/api/v1/webauthn_verification/odow34b93t6aPCdY"
120
+ response_fail = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry."
121
+ yank_uri = "http://example/api/v1/gems/yank"
122
+ webauthn_uri = "http://example/api/v1/webauthn_verification"
123
+ port = 5678
124
+ server = TCPServer.new(port)
125
+
126
+ @fetcher.data[webauthn_uri] = HTTPResponseFactory.create(body: webauthn_verification_url, code: 200, msg: "OK")
127
+ @fetcher.data[yank_uri] = [
128
+ HTTPResponseFactory.create(body: response_fail, code: 401, msg: "Unauthorized"),
129
+ HTTPResponseFactory.create(body: "Successfully yanked", code: 200, msg: "OK"),
130
+ ]
131
+
132
+ @cmd.options[:args] = %w[a]
133
+ @cmd.options[:added_platform] = true
134
+ @cmd.options[:version] = req("= 1.0")
135
+
136
+ TCPServer.stub(:new, server) do
137
+ Gem::WebauthnListener.stub(:wait_for_otp_code, "Uvh6T57tkWuUnWYo") do
138
+ use_ui @ui do
139
+ @cmd.execute
140
+ end
141
+ end
142
+ ensure
143
+ server.close
144
+ end
145
+
146
+ url_with_port = "#{webauthn_verification_url}?port=#{port}"
147
+ assert_match %r{Yanking gem from http://example}, @ui.output
148
+ assert_match "You have enabled multi-factor authentication. Please visit #{url_with_port} to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin command with the `--otp [your_code]` option.", @ui.output
149
+ assert_match "You are verified with a security device. You may close the browser window.", @ui.output
150
+ assert_equal "Uvh6T57tkWuUnWYo", @fetcher.last_request["OTP"]
151
+ assert_match "Successfully yanked", @ui.output
152
+ end
153
+
154
+ def test_with_webauthn_enabled_failure
155
+ webauthn_verification_url = "http://example/api/v1/webauthn_verification/odow34b93t6aPCdY"
156
+ response_fail = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry."
157
+ yank_uri = "http://example/api/v1/gems/yank"
158
+ webauthn_uri = "http://example/api/v1/webauthn_verification"
159
+ port = 5678
160
+ server = TCPServer.new(port)
161
+ raise_error = ->(*_args) { raise Gem::WebauthnVerificationError, "Something went wrong" }
162
+
163
+ @fetcher.data[webauthn_uri] = HTTPResponseFactory.create(body: webauthn_verification_url, code: 200, msg: "OK")
164
+ @fetcher.data[yank_uri] = [
165
+ HTTPResponseFactory.create(body: response_fail, code: 401, msg: "Unauthorized"),
166
+ HTTPResponseFactory.create(body: "Successfully yanked", code: 200, msg: "OK"),
167
+ ]
168
+
169
+ @cmd.options[:args] = %w[a]
170
+ @cmd.options[:added_platform] = true
171
+ @cmd.options[:version] = req("= 1.0")
172
+
173
+ error = assert_raise Gem::MockGemUi::TermError do
174
+ TCPServer.stub(:new, server) do
175
+ Gem::WebauthnListener.stub(:wait_for_otp_code, raise_error) do
176
+ use_ui @ui do
177
+ @cmd.execute
178
+ end
179
+ end
180
+ ensure
181
+ server.close
182
+ end
183
+ end
184
+ assert_equal 1, error.exit_code
185
+
186
+ url_with_port = "#{webauthn_verification_url}?port=#{port}"
187
+
188
+ assert_match @fetcher.last_request["Authorization"], Gem.configuration.rubygems_api_key
189
+ assert_match %r{Yanking gem from http://example}, @ui.output
190
+ assert_match "You have enabled multi-factor authentication. Please visit #{url_with_port} to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin command with the `--otp [your_code]` option.", @ui.output
191
+ assert_match "ERROR: Security device verification failed: Something went wrong", @ui.error
192
+ refute_match "You are verified with a security device. You may close the browser window.", @ui.output
193
+ refute_match "Successfully yanked", @ui.output
194
+ end
195
+
112
196
  def test_execute_key
113
197
  yank_uri = "http://example/api/v1/gems/yank"
114
198
  @fetcher.data[yank_uri] = HTTPResponseFactory.create(body: "Successfully yanked", code: 200, msg: "OK")
@@ -145,6 +145,7 @@ class TestGemExtCargoBuilder < Gem::TestCase
145
145
  system(@rust_envs, "cargo", "-V", out: IO::NULL, err: [:child, :out])
146
146
  pend "cargo not present" unless $?.success?
147
147
  pend "ruby.h is not provided by ruby repo" if ruby_repo?
148
+ pend "rust toolchain of mingw is broken" if mingw_windows?
148
149
  end
149
150
 
150
151
  def assert_ffi_handle(bundle, name)
@@ -3,9 +3,6 @@ require_relative "helper"
3
3
 
4
4
  class TestGemGemRunner < Gem::TestCase
5
5
  def setup
6
- @orig_gem_home = ENV["GEM_HOME"]
7
- ENV["GEM_HOME"] = @gemhome
8
-
9
6
  require "rubygems/command"
10
7
  @orig_args = Gem::Command.build_args
11
8
  @orig_specific_extra_args = Gem::Command.specific_extra_args_hash.dup
@@ -13,18 +10,21 @@ class TestGemGemRunner < Gem::TestCase
13
10
 
14
11
  super
15
12
 
13
+ @orig_gem_home = ENV["GEM_HOME"]
14
+ ENV["GEM_HOME"] = @gemhome
15
+
16
16
  require "rubygems/gem_runner"
17
17
  @runner = Gem::GemRunner.new
18
18
  end
19
19
 
20
20
  def teardown
21
+ ENV["GEM_HOME"] = @orig_gem_home
22
+
21
23
  super
22
24
 
23
25
  Gem::Command.build_args = @orig_args
24
26
  Gem::Command.specific_extra_args_hash = @orig_specific_extra_args
25
27
  Gem::Command.extra_args = @orig_extra_args
26
-
27
- ENV["GEM_HOME"] = @orig_gem_home
28
28
  end
29
29
 
30
30
  def test_do_configuration
@@ -230,10 +230,77 @@ class TestGemGemcutterUtilities < Gem::TestCase
230
230
  assert_equal "111111", @fetcher.last_request["OTP"]
231
231
  end
232
232
 
233
- def util_sign_in(response, host = nil, args = [], extra_input = "")
234
- email = "you@example.com"
235
- password = "secret"
236
- profile_response = HTTPResponseFactory.create(body: "mfa: disabled\n", code: 200, msg: "OK")
233
+ def test_sign_in_with_webauthn_enabled
234
+ webauthn_verification_url = "rubygems.org/api/v1/webauthn_verification/odow34b93t6aPCdY"
235
+ response_fail = "You have enabled multifactor authentication"
236
+ api_key = "a5fdbb6ba150cbb83aad2bb2fede64cf040453903"
237
+ port = 5678
238
+ server = TCPServer.new(port)
239
+
240
+ TCPServer.stub(:new, server) do
241
+ Gem::WebauthnListener.stub(:wait_for_otp_code, "Uvh6T57tkWuUnWYo") do
242
+ util_sign_in(proc do
243
+ @call_count ||= 0
244
+ if (@call_count += 1).odd?
245
+ HTTPResponseFactory.create(body: response_fail, code: 401, msg: "Unauthorized")
246
+ else
247
+ HTTPResponseFactory.create(body: api_key, code: 200, msg: "OK")
248
+ end
249
+ end, nil, [], "", webauthn_verification_url)
250
+ end
251
+ ensure
252
+ server.close
253
+ end
254
+
255
+ url_with_port = "#{webauthn_verification_url}?port=#{port}"
256
+ assert_match "You have enabled multi-factor authentication. Please visit #{url_with_port} to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin command with the `--otp [your_code]` option.", @sign_in_ui.output
257
+ assert_match "You are verified with a security device. You may close the browser window.", @sign_in_ui.output
258
+ assert_equal "Uvh6T57tkWuUnWYo", @fetcher.last_request["OTP"]
259
+ end
260
+
261
+ def test_sign_in_with_webauthn_enabled_with_error
262
+ webauthn_verification_url = "rubygems.org/api/v1/webauthn_verification/odow34b93t6aPCdY"
263
+ response_fail = "You have enabled multifactor authentication"
264
+ api_key = "a5fdbb6ba150cbb83aad2bb2fede64cf040453903"
265
+ port = 5678
266
+ server = TCPServer.new(port)
267
+ raise_error = ->(*_args) { raise Gem::WebauthnVerificationError, "Something went wrong" }
268
+
269
+ error = assert_raise Gem::MockGemUi::TermError do
270
+ TCPServer.stub(:new, server) do
271
+ Gem::WebauthnListener.stub(:wait_for_otp_code, raise_error) do
272
+ util_sign_in(proc do
273
+ @call_count ||= 0
274
+ if (@call_count += 1).odd?
275
+ HTTPResponseFactory.create(body: response_fail, code: 401, msg: "Unauthorized")
276
+ else
277
+ HTTPResponseFactory.create(body: api_key, code: 200, msg: "OK")
278
+ end
279
+ end, nil, [], "", webauthn_verification_url)
280
+ end
281
+ ensure
282
+ server.close
283
+ end
284
+ end
285
+ assert_equal 1, error.exit_code
286
+
287
+ url_with_port = "#{webauthn_verification_url}?port=#{port}"
288
+ assert_match "You have enabled multi-factor authentication. Please visit #{url_with_port} to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin command with the `--otp [your_code]` option.", @sign_in_ui.output
289
+ assert_match "ERROR: Security device verification failed: Something went wrong", @sign_in_ui.error
290
+ refute_match "You are verified with a security device. You may close the browser window.", @sign_in_ui.output
291
+ refute_match "Signed in with API key:", @sign_in_ui.output
292
+ end
293
+
294
+ def util_sign_in(response, host = nil, args = [], extra_input = "", webauthn_url = nil)
295
+ email = "you@example.com"
296
+ password = "secret"
297
+ profile_response = HTTPResponseFactory.create(body: "mfa: disabled\n", code: 200, msg: "OK")
298
+ webauthn_response =
299
+ if webauthn_url
300
+ HTTPResponseFactory.create(body: webauthn_url, code: 200, msg: "OK")
301
+ else
302
+ HTTPResponseFactory.create(body: "You don't have any security devices", code: 422, msg: "Unprocessable Entity")
303
+ end
237
304
 
238
305
  if host
239
306
  ENV["RUBYGEMS_HOST"] = host
@@ -244,6 +311,7 @@ class TestGemGemcutterUtilities < Gem::TestCase
244
311
  @fetcher = Gem::FakeFetcher.new
245
312
  @fetcher.data["#{host}/api/v1/api_key"] = response
246
313
  @fetcher.data["#{host}/api/v1/profile/me.yaml"] = profile_response
314
+ @fetcher.data["#{host}/api/v1/webauthn_verification"] = webauthn_response
247
315
  Gem::RemoteFetcher.fetcher = @fetcher
248
316
 
249
317
  @sign_in_ui = Gem::MockGemUi.new("#{email}\n#{password}\n\n\n\n\n\n\n\n\n" + extra_input)
@@ -766,7 +766,7 @@ gem 'other', version
766
766
  def test_generate_plugins
767
767
  installer = util_setup_installer do |spec|
768
768
  write_file File.join(@tempdir, "lib", "rubygems_plugin.rb") do |io|
769
- io.write "puts __FILE__"
769
+ io.write "# do nothing"
770
770
  end
771
771
 
772
772
  spec.files += %w[lib/rubygems_plugin.rb]
@@ -853,11 +853,59 @@ gem 'other', version
853
853
  refute_includes File.read(build_root_path), build_root
854
854
  end
855
855
 
856
+ class << self
857
+ attr_accessor :plugin_loaded
858
+ attr_accessor :post_install_is_called
859
+ end
860
+
861
+ def test_use_plugin_immediately
862
+ self.class.plugin_loaded = false
863
+ self.class.post_install_is_called = false
864
+ spec_version = nil
865
+ plugin_path = nil
866
+ installer = util_setup_installer do |spec|
867
+ spec_version = spec.version
868
+ plugin_path = File.join("lib", "rubygems_plugin.rb")
869
+ write_file File.join(@tempdir, plugin_path) do |io|
870
+ io.write <<-PLUGIN
871
+ #{self.class}.plugin_loaded = true
872
+ Gem.post_install do
873
+ #{self.class}.post_install_is_called = true
874
+ end
875
+ PLUGIN
876
+ end
877
+ spec.files += [plugin_path]
878
+ plugin_path = File.join(spec.gem_dir, plugin_path)
879
+ end
880
+ build_rake_in do
881
+ installer.install
882
+ end
883
+ assert self.class.plugin_loaded, "plugin is not loaded"
884
+ assert self.class.post_install_is_called,
885
+ "post install hook registered by plugin is not called"
886
+
887
+ self.class.plugin_loaded = false
888
+ $LOADED_FEATURES.delete(plugin_path)
889
+ installer_new = util_setup_installer do |spec_new|
890
+ spec_new.version = spec_version.version.succ
891
+ plugin_path = File.join("lib", "rubygems_plugin.rb")
892
+ write_file File.join(@tempdir, plugin_path) do |io|
893
+ io.write "#{self.class}.plugin_loaded = true"
894
+ end
895
+ spec_new.files += [plugin_path]
896
+ end
897
+ build_rake_in do
898
+ installer_new.install
899
+ end
900
+ assert !self.class.plugin_loaded,
901
+ "plugin is loaded even when old version is already loaded"
902
+ end
903
+
856
904
  def test_keeps_plugins_up_to_date
857
905
  # NOTE: version a-2 is already installed by setup hooks
858
906
 
859
907
  write_file File.join(@tempdir, "lib", "rubygems_plugin.rb") do |io|
860
- io.write "puts __FILE__"
908
+ io.write "# do nothing"
861
909
  end
862
910
 
863
911
  build_rake_in do
@@ -172,7 +172,7 @@ class TestGemUninstaller < Gem::InstallerTestCase
172
172
 
173
173
  def test_remove_plugins
174
174
  write_file File.join(@tempdir, "lib", "rubygems_plugin.rb") do |io|
175
- io.write "puts __FILE__"
175
+ io.write "# do nothing"
176
176
  end
177
177
 
178
178
  @spec.files += %w[lib/rubygems_plugin.rb]
@@ -189,7 +189,7 @@ class TestGemUninstaller < Gem::InstallerTestCase
189
189
 
190
190
  def test_remove_plugins_with_install_dir
191
191
  write_file File.join(@tempdir, "lib", "rubygems_plugin.rb") do |io|
192
- io.write "puts __FILE__"
192
+ io.write "# do nothing"
193
193
  end
194
194
 
195
195
  @spec.files += %w[lib/rubygems_plugin.rb]
@@ -207,7 +207,7 @@ class TestGemUninstaller < Gem::InstallerTestCase
207
207
 
208
208
  def test_regenerate_plugins_for
209
209
  write_file File.join(@tempdir, "lib", "rubygems_plugin.rb") do |io|
210
- io.write "puts __FILE__"
210
+ io.write "# do nothing"
211
211
  end
212
212
 
213
213
  @spec.files += %w[lib/rubygems_plugin.rb]
@@ -634,7 +634,7 @@ create_makefile '#{@spec.name}'
634
634
 
635
635
  def test_uninstall_keeps_plugins_up_to_date
636
636
  write_file File.join(@tempdir, "lib", "rubygems_plugin.rb") do |io|
637
- io.write "puts __FILE__"
637
+ io.write "# do nothing"
638
638
  end
639
639
 
640
640
  plugin_path = File.join Gem.plugindir, "a_plugin.rb"
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  require_relative "helper"
3
3
 
4
- class TestKernel < Gem::TestCase
4
+ class TestGemKernel < Gem::TestCase
5
5
  def setup
6
6
  super
7
7
 
@@ -3,13 +3,36 @@
3
3
  require_relative "helper"
4
4
  require "open3"
5
5
 
6
- class TestProjectSanity < Gem::TestCase
6
+ class TestGemProjectSanity < Gem::TestCase
7
+ def setup
8
+ end
9
+
10
+ def teardown
11
+ end
12
+
7
13
  def test_manifest_is_up_to_date
8
- pend unless File.exist?(File.expand_path("../../Rakefile", __dir__))
14
+ pend unless File.exist?("#{root}/Rakefile")
9
15
 
10
16
  _, status = Open3.capture2e("rake check_manifest")
11
17
 
12
- assert status.success?, "Expected Manifest.txt to be up to date, but it's not. Run `rake update_manifest` to sync it."
18
+ unless status.success?
19
+ original_contents = File.read("#{root}/Manifest.txt")
20
+
21
+ # Update the manifest to see if it fixes the problem
22
+ Open3.capture2e("rake update_manifest")
23
+
24
+ out, status = Open3.capture2e("rake check_manifest")
25
+
26
+ # If `rake update_manifest` fixed the problem, that was the original
27
+ # issue, otherwise it was an unknown error, so print the error output
28
+ if status.success?
29
+ File.write("#{root}/Manifest.txt", original_contents)
30
+
31
+ raise "Expected Manifest.txt to be up to date, but it's not. Run `rake update_manifest` to sync it."
32
+ else
33
+ raise "There was an error running `rake check_manifest`: #{out}"
34
+ end
35
+ end
13
36
  end
14
37
 
15
38
  def test_require_rubygems_package
@@ -17,4 +40,10 @@ class TestProjectSanity < Gem::TestCase
17
40
 
18
41
  assert status.success?, err
19
42
  end
43
+
44
+ private
45
+
46
+ def root
47
+ File.expand_path("../..", __dir__)
48
+ end
20
49
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  require_relative "helper"
3
3
 
4
- class TestRemoteFetchError < Gem::TestCase
4
+ class TestGemRemoteFetchError < Gem::TestCase
5
5
  def test_password_redacted
6
6
  error = Gem::RemoteFetcher::FetchError.new("There was an error fetching", "https://user:secret@gemsource.org")
7
7
  refute_match %r{secret}, error.to_s