rubygems-update 3.0.4 → 3.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/History.txt +53 -0
  3. data/Manifest.txt +3 -1
  4. data/Rakefile +7 -5
  5. data/bundler/lib/bundler/build_metadata.rb +1 -1
  6. data/lib/rubygems.rb +6 -12
  7. data/lib/rubygems/commands/environment_command.rb +0 -3
  8. data/lib/rubygems/commands/push_command.rb +2 -0
  9. data/lib/rubygems/commands/uninstall_command.rb +16 -6
  10. data/lib/rubygems/commands/which_command.rb +1 -3
  11. data/lib/rubygems/defaults.rb +1 -8
  12. data/lib/rubygems/dependency_installer.rb +1 -2
  13. data/lib/rubygems/exceptions.rb +0 -4
  14. data/lib/rubygems/gemcutter_utilities.rb +9 -5
  15. data/lib/rubygems/installer.rb +1 -1
  16. data/lib/rubygems/installer_test_case.rb +2 -2
  17. data/lib/rubygems/package/tar_header.rb +11 -2
  18. data/lib/rubygems/remote_fetcher.rb +15 -54
  19. data/lib/rubygems/request.rb +1 -1
  20. data/lib/rubygems/request_set/gem_dependency_api.rb +3 -5
  21. data/lib/rubygems/s3_uri_signer.rb +175 -0
  22. data/lib/rubygems/security_option.rb +0 -1
  23. data/lib/rubygems/specification.rb +0 -1
  24. data/lib/rubygems/stub_specification.rb +1 -2
  25. data/lib/rubygems/test_case.rb +8 -4
  26. data/lib/rubygems/util.rb +12 -0
  27. data/rubygems-update.gemspec +1 -1
  28. data/test/rubygems/test_gem.rb +6 -3
  29. data/test/rubygems/test_gem_commands_environment_command.rb +0 -11
  30. data/test/rubygems/test_gem_commands_push_command.rb +15 -0
  31. data/test/rubygems/test_gem_commands_uninstall_command.rb +80 -1
  32. data/test/rubygems/test_gem_indexer.rb +8 -8
  33. data/test/rubygems/test_gem_installer.rb +48 -17
  34. data/test/rubygems/test_gem_package_tar_header.rb +41 -0
  35. data/test/rubygems/test_gem_remote_fetcher.rb +133 -14
  36. data/test/rubygems/test_gem_request.rb +4 -4
  37. data/test/rubygems/test_gem_request_set_gem_dependency_api.rb +20 -30
  38. data/test/rubygems/test_gem_util.rb +8 -0
  39. data/util/cops/deprecations.rb +52 -0
  40. data/util/create_certs.sh +27 -0
  41. metadata +5 -3
  42. data/lib/rubygems/compatibility.rb +0 -40
@@ -283,7 +283,7 @@ class Gem::Request
283
283
  end
284
284
  ua << ")"
285
285
 
286
- ua << " #{RUBY_ENGINE}" if defined?(RUBY_ENGINE) and RUBY_ENGINE != 'ruby'
286
+ ua << " #{RUBY_ENGINE}" if RUBY_ENGINE != 'ruby'
287
287
 
288
288
  ua
289
289
  end
@@ -782,7 +782,7 @@ Gem dependencies file #{@path} includes git reference for both ref/branch and ta
782
782
  # You may also provide +engine:+ and +engine_version:+ options to restrict
783
783
  # this gem dependencies file to a particular ruby engine and its engine
784
784
  # version. This matching is performed by using the RUBY_ENGINE and
785
- # engine_specific VERSION constants. (For JRuby, JRUBY_VERSION).
785
+ # RUBY_ENGINE_VERSION constants.
786
786
 
787
787
  def ruby(version, options = {})
788
788
  engine = options[:engine]
@@ -809,11 +809,9 @@ Gem dependencies file #{@path} includes git reference for both ref/branch and ta
809
809
  end
810
810
 
811
811
  if engine_version
812
- my_engine_version = Object.const_get "#{Gem.ruby_engine.upcase}_VERSION"
813
-
814
- if engine_version != my_engine_version
812
+ if engine_version != RUBY_ENGINE_VERSION
815
813
  message =
816
- "Your Ruby engine version is #{Gem.ruby_engine} #{my_engine_version}, " +
814
+ "Your Ruby engine version is #{Gem.ruby_engine} #{RUBY_ENGINE_VERSION}, " +
817
815
  "but your #{gem_deps_file} requires #{engine} #{engine_version}"
818
816
 
819
817
  raise Gem::RubyVersionMismatch, message
@@ -0,0 +1,175 @@
1
+ require 'base64'
2
+ require 'digest'
3
+ require 'openssl'
4
+
5
+ ##
6
+ # S3URISigner implements AWS SigV4 for S3 Source to avoid a dependency on the aws-sdk-* gems
7
+ # More on AWS SigV4: https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html
8
+ class Gem::S3URISigner
9
+
10
+ class ConfigurationError < Gem::Exception
11
+
12
+ def initialize(message)
13
+ super message
14
+ end
15
+
16
+ def to_s # :nodoc:
17
+ "#{super}"
18
+ end
19
+
20
+ end
21
+
22
+ class InstanceProfileError < Gem::Exception
23
+
24
+ def initialize(message)
25
+ super message
26
+ end
27
+
28
+ def to_s # :nodoc:
29
+ "#{super}"
30
+ end
31
+
32
+ end
33
+
34
+ attr_accessor :uri
35
+
36
+ def initialize(uri)
37
+ @uri = uri
38
+ end
39
+
40
+ ##
41
+ # Signs S3 URI using query-params according to the reference: https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
42
+ def sign(expiration = 86400)
43
+ s3_config = fetch_s3_config
44
+
45
+ current_time = Time.now.utc
46
+ date_time = current_time.strftime("%Y%m%dT%H%m%SZ")
47
+ date = date_time[0,8]
48
+
49
+ credential_info = "#{date}/#{s3_config.region}/s3/aws4_request"
50
+ canonical_host = "#{uri.host}.s3.#{s3_config.region}.amazonaws.com"
51
+
52
+ query_params = generate_canonical_query_params(s3_config, date_time, credential_info, expiration)
53
+ canonical_request = generate_canonical_request(canonical_host, query_params)
54
+ string_to_sign = generate_string_to_sign(date_time, credential_info, canonical_request)
55
+ signature = generate_signature(s3_config, date, string_to_sign)
56
+
57
+ URI.parse("https://#{canonical_host}#{uri.path}?#{query_params}&X-Amz-Signature=#{signature}")
58
+ end
59
+
60
+ private
61
+
62
+ S3Config = Struct.new :access_key_id, :secret_access_key, :security_token, :region
63
+
64
+ def generate_canonical_query_params(s3_config, date_time, credential_info, expiration)
65
+ canonical_params = {}
66
+ canonical_params["X-Amz-Algorithm"] = "AWS4-HMAC-SHA256"
67
+ canonical_params["X-Amz-Credential"] = "#{s3_config.access_key_id}/#{credential_info}"
68
+ canonical_params["X-Amz-Date"] = date_time
69
+ canonical_params["X-Amz-Expires"] = expiration.to_s
70
+ canonical_params["X-Amz-SignedHeaders"] = "host"
71
+ canonical_params["X-Amz-Security-Token"] = s3_config.security_token if s3_config.security_token
72
+
73
+ # Sorting is required to generate proper signature
74
+ canonical_params.sort.to_h.map do |key, value|
75
+ "#{base64_uri_escape(key)}=#{base64_uri_escape(value)}"
76
+ end.join("&")
77
+ end
78
+
79
+ def generate_canonical_request(canonical_host, query_params)
80
+ [
81
+ "GET",
82
+ uri.path,
83
+ query_params,
84
+ "host:#{canonical_host}",
85
+ "", # empty params
86
+ "host",
87
+ "UNSIGNED-PAYLOAD",
88
+ ].join("\n")
89
+ end
90
+
91
+ def generate_string_to_sign(date_time, credential_info, canonical_request)
92
+ [
93
+ "AWS4-HMAC-SHA256",
94
+ date_time,
95
+ credential_info,
96
+ Digest::SHA256.hexdigest(canonical_request)
97
+ ].join("\n")
98
+ end
99
+
100
+ def generate_signature(s3_config, date, string_to_sign)
101
+ date_key = OpenSSL::HMAC.digest("sha256", "AWS4" + s3_config.secret_access_key, date)
102
+ date_region_key = OpenSSL::HMAC.digest("sha256", date_key, s3_config.region)
103
+ date_region_service_key = OpenSSL::HMAC.digest("sha256", date_region_key, "s3")
104
+ signing_key = OpenSSL::HMAC.digest("sha256", date_region_service_key, "aws4_request")
105
+ OpenSSL::HMAC.hexdigest("sha256", signing_key, string_to_sign)
106
+ end
107
+
108
+ ##
109
+ # Extracts S3 configuration for S3 bucket
110
+ def fetch_s3_config
111
+ return S3Config.new(uri.user, uri.password, nil, "us-east-1") if uri.user && uri.password
112
+
113
+ s3_source = Gem.configuration[:s3_source] || Gem.configuration["s3_source"]
114
+ host = uri.host
115
+ raise ConfigurationError.new("no s3_source key exists in .gemrc") unless s3_source
116
+
117
+ auth = s3_source[host] || s3_source[host.to_sym]
118
+ raise ConfigurationError.new("no key for host #{host} in s3_source in .gemrc") unless auth
119
+
120
+ provider = auth[:provider] || auth["provider"]
121
+ case provider
122
+ when "env"
123
+ id = ENV["AWS_ACCESS_KEY_ID"]
124
+ secret = ENV["AWS_SECRET_ACCESS_KEY"]
125
+ security_token = ENV["AWS_SESSION_TOKEN"]
126
+ when "instance_profile"
127
+ credentials = ec2_metadata_credentials_json
128
+ id = credentials["AccessKeyId"]
129
+ secret = credentials["SecretAccessKey"]
130
+ security_token = credentials["Token"]
131
+ else
132
+ id = auth[:id] || auth["id"]
133
+ secret = auth[:secret] || auth["secret"]
134
+ security_token = auth[:security_token] || auth["security_token"]
135
+ end
136
+
137
+ raise ConfigurationError.new("s3_source for #{host} missing id or secret") unless id && secret
138
+
139
+ region = auth[:region] || auth["region"] || "us-east-1"
140
+ S3Config.new(id, secret, security_token, region)
141
+ end
142
+
143
+ def base64_uri_escape(str)
144
+ str.gsub(/[\+\/=\n]/, BASE64_URI_TRANSLATE)
145
+ end
146
+
147
+ def ec2_metadata_credentials_json
148
+ require 'net/http'
149
+ require 'rubygems/request'
150
+ require 'rubygems/request/connection_pools'
151
+ require 'json'
152
+
153
+ metadata_uri = URI(EC2_METADATA_CREDENTIALS)
154
+ @request_pool ||= create_request_pool(metadata_uri)
155
+ request = Gem::Request.new(metadata_uri, Net::HTTP::Get, nil, @request_pool)
156
+ response = request.fetch
157
+
158
+ case response
159
+ when Net::HTTPOK then
160
+ JSON.parse(response.body)
161
+ else
162
+ raise InstanceProfileError.new("Unable to fetch AWS credentials from #{metadata_uri}: #{response.message} #{response.code}")
163
+ end
164
+ end
165
+
166
+ def create_request_pool(uri)
167
+ proxy_uri = Gem::Request.proxy_uri(Gem::Request.get_proxy_from_env(uri.scheme))
168
+ certs = Gem::Request.get_cert_files
169
+ Gem::Request::ConnectionPools.new(proxy_uri, certs).pool_for(uri)
170
+ end
171
+
172
+ BASE64_URI_TRANSLATE = { "+" => "%2B", "/" => "%2F", "=" => "%3D", "\n" => "" }.freeze
173
+ EC2_METADATA_CREDENTIALS = "http://169.254.169.254/latest/meta-data/identity-credentials/ec2/security-credentials/ec2-instance".freeze
174
+
175
+ end
@@ -19,7 +19,6 @@ end
19
19
 
20
20
  module Gem::SecurityOption
21
21
  def add_security_option
22
- # TODO: use @parser.accept
23
22
  OptionParser.accept Gem::Security::Policy do |value|
24
23
  require 'rubygems/security'
25
24
 
@@ -2284,7 +2284,6 @@ class Gem::Specification < Gem::BasicSpecification
2284
2284
 
2285
2285
  e = Gem::LoadError.new msg
2286
2286
  e.name = self.name
2287
- # TODO: e.requirement = dep.requirement
2288
2287
 
2289
2288
  raise e
2290
2289
  end
@@ -110,8 +110,7 @@ class Gem::StubSpecification < Gem::BasicSpecification
110
110
  begin
111
111
  saved_lineno = $.
112
112
 
113
- # TODO It should be use `File.open`, but bundler-1.16.1 example expects Kernel#open.
114
- open loaded_from, OPEN_MODE do |file|
113
+ File.open loaded_from, OPEN_MODE do |file|
115
114
  begin
116
115
  file.readline # discard encoding line
117
116
  stubline = file.readline.chomp
@@ -140,6 +140,12 @@ class Gem::TestCase < (defined?(Minitest::Test) ? Minitest::Test : MiniTest::Uni
140
140
  assert File.exist?(path), msg
141
141
  end
142
142
 
143
+ def assert_directory_exists(path, msg = nil)
144
+ msg = message(msg) { "Expected path '#{path}' to be a directory" }
145
+ assert_path_exists path
146
+ assert File.directory?(path), msg
147
+ end
148
+
143
149
  ##
144
150
  # Sets the ENABLE_SHARED entry in RbConfig::CONFIG to +value+ and restores
145
151
  # the original value when the block ends
@@ -256,6 +262,7 @@ class Gem::TestCase < (defined?(Minitest::Test) ? Minitest::Test : MiniTest::Uni
256
262
  @orig_gem_env_requirements = ENV.to_hash
257
263
 
258
264
  ENV['GEM_VENDOR'] = nil
265
+ ENV['GEMRC'] = nil
259
266
  ENV['SOURCE_DATE_EPOCH'] = nil
260
267
 
261
268
  @current_dir = Dir.pwd
@@ -746,7 +753,7 @@ class Gem::TestCase < (defined?(Minitest::Test) ? Minitest::Test : MiniTest::Uni
746
753
  # Removes all installed gems from +@gemhome+.
747
754
 
748
755
  def util_clear_gems
749
- FileUtils.rm_rf File.join(@gemhome, "gems") # TODO: use Gem::Dirs
756
+ FileUtils.rm_rf File.join(@gemhome, "gems")
750
757
  FileUtils.mkdir File.join(@gemhome, "gems")
751
758
  FileUtils.rm_rf File.join(@gemhome, "specifications")
752
759
  FileUtils.mkdir File.join(@gemhome, "specifications")
@@ -931,9 +938,6 @@ class Gem::TestCase < (defined?(Minitest::Test) ? Minitest::Test : MiniTest::Uni
931
938
  # location are returned.
932
939
 
933
940
  def util_gem(name, version, deps = nil, &block)
934
- # TODO: deprecate
935
- raise "deps or block, not both" if deps and block
936
-
937
941
  if deps
938
942
  block = proc do |s|
939
943
  # Since Hash#each is unordered in 1.8, sort
@@ -128,4 +128,16 @@ module Gem::Util
128
128
  end
129
129
  end
130
130
 
131
+ ##
132
+ # Corrects +path+ (usually returned by `URI.parse().path` on Windows), that
133
+ # comes with a leading slash.
134
+
135
+ def self.correct_for_windows_path(path)
136
+ if path[0].chr == '/' && path[1].chr =~ /[a-z]/i && path[2].chr == ':'
137
+ path[1..-1]
138
+ else
139
+ path
140
+ end
141
+ end
142
+
131
143
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "rubygems-update"
5
- s.version = "3.0.4"
5
+ s.version = "3.0.5"
6
6
  s.authors = ["Jim Weirich", "Chad Fowler", "Eric Hodel", "Luis Lavena", "Aaron Patterson", "Samuel Giddins", "André Arko", "Evan Phoenix", "Hiroshi SHIBATA"]
7
7
  s.email = ["", "", "drbrain@segment7.net", "luislavena@gmail.com", "aaron@tenderlovemaking.com", "segiddins@segiddins.me", "andre@arko.net", "evan@phx.io", "hsbt@ruby-lang.org"]
8
8
 
@@ -156,7 +156,7 @@ class TestGem < Gem::TestCase
156
156
  end
157
157
 
158
158
  def assert_self_install_permissions
159
- mask = /mingw|mswin/ =~ RUBY_PLATFORM ? 0700 : 0777
159
+ mask = win_platform? ? 0700 : 0777
160
160
  options = {
161
161
  :dir_mode => 0500,
162
162
  :prog_mode => 0510,
@@ -195,6 +195,9 @@ class TestGem < Gem::TestCase
195
195
  'gems/foo-1/bin/foo.cmd' => prog_mode,
196
196
  'gems/foo-1/data/foo.txt' => data_mode,
197
197
  }
198
+ # below is for intermittent errors on Appveyor & Travis 2019-01,
199
+ # see https://github.com/rubygems/rubygems/pull/2568
200
+ sleep 0.2
198
201
  result = {}
199
202
  Dir.chdir @gemhome do
200
203
  expected.each_key do |n|
@@ -541,7 +544,7 @@ class TestGem < Gem::TestCase
541
544
 
542
545
  Gem.ensure_gem_subdirectories @gemhome, 0750
543
546
 
544
- assert File.directory? File.join(@gemhome, "cache")
547
+ assert_directory_exists File.join(@gemhome, "cache")
545
548
 
546
549
  assert_equal 0750, File::Stat.new(@gemhome).mode & 0777
547
550
  assert_equal 0750, File::Stat.new(File.join(@gemhome, "cache")).mode & 0777
@@ -570,7 +573,7 @@ class TestGem < Gem::TestCase
570
573
 
571
574
  Gem.ensure_gem_subdirectories gemdir
572
575
 
573
- assert File.directory?(util_cache_dir)
576
+ assert_directory_exists util_cache_dir
574
577
  end
575
578
 
576
579
  unless win_platform? || Process.uid.zero? # only for FS that support write protection
@@ -90,17 +90,6 @@ class TestGemCommandsEnvironmentCommand < Gem::TestCase
90
90
  assert_equal '', @ui.error
91
91
  end
92
92
 
93
- def test_execute_packageversion
94
- @cmd.send :handle_options, %w[packageversion]
95
-
96
- use_ui @ui do
97
- @cmd.execute
98
- end
99
-
100
- assert_equal "#{Gem::RubyGemsPackageVersion}\n", @ui.output
101
- assert_equal '', @ui.error
102
- end
103
-
104
93
  def test_execute_remotesources
105
94
  orig_sources = Gem.sources.dup
106
95
  Gem.sources.replace %w[http://gems.example.com]
@@ -199,6 +199,21 @@ class TestGemCommandsPushCommand < Gem::TestCase
199
199
  send_battery
200
200
  end
201
201
 
202
+ def test_sending_gem_with_env_var_api_key
203
+ @host = "http://privategemserver.example"
204
+
205
+ @spec, @path = util_gem "freebird", "1.0.1" do |spec|
206
+ spec.metadata['allowed_push_host'] = @host
207
+ end
208
+
209
+ @api_key = "PRIVKEY"
210
+ ENV["GEM_HOST_API_KEY"] = "PRIVKEY"
211
+
212
+ @response = "Successfully registered gem: freebird (1.0.1)"
213
+ @fetcher.data["#{@host}/api/v1/gems"] = [@response, 200, 'OK']
214
+ send_battery
215
+ end
216
+
202
217
  def test_sending_gem_to_allowed_push_host_with_basic_credentials
203
218
  @sanitized_host = "http://privategemserver.example"
204
219
  @host = "http://user:password@privategemserver.example"
@@ -192,6 +192,62 @@ class TestGemCommandsUninstallCommand < Gem::InstallerTestCase
192
192
  assert File.exist? File.join(@gemhome, 'bin', 'executable')
193
193
  end
194
194
 
195
+ def test_uninstall_selection
196
+ ui = Gem::MockGemUi.new "1\n"
197
+
198
+ util_make_gems
199
+
200
+ list = Gem::Specification.find_all_by_name 'a'
201
+
202
+ @cmd.options[:args] = ['a']
203
+
204
+ use_ui ui do
205
+ @cmd.execute
206
+ end
207
+
208
+ updated_list = Gem::Specification.find_all_by_name('a')
209
+ assert_equal list.length - 1, updated_list.length
210
+
211
+ assert_match ' 1. a-1', ui.output
212
+ assert_match ' 2. a-2', ui.output
213
+ assert_match ' 3. a-3.a', ui.output
214
+ assert_match ' 4. All versions', ui.output
215
+ assert_match 'uninstalled a-1', ui.output
216
+ end
217
+
218
+ def test_uninstall_selection_multiple_gems
219
+ ui = Gem::MockGemUi.new "1\n"
220
+
221
+ util_make_gems
222
+
223
+ a_list = Gem::Specification.find_all_by_name('a')
224
+ b_list = Gem::Specification.find_all_by_name('b')
225
+ list = a_list + b_list
226
+
227
+ @cmd.options[:args] = ['a', 'b']
228
+
229
+ use_ui ui do
230
+ @cmd.execute
231
+ end
232
+
233
+ updated_a_list = Gem::Specification.find_all_by_name('a')
234
+ updated_b_list = Gem::Specification.find_all_by_name('b')
235
+ updated_list = updated_a_list + updated_b_list
236
+
237
+ assert_equal list.length - 2, updated_list.length
238
+
239
+ out = ui.output.split("\n")
240
+ assert_match 'uninstalled b-2', out.shift
241
+ assert_match '', out.shift
242
+ assert_match 'Select gem to uninstall:', out.shift
243
+ assert_match ' 1. a-1', out.shift
244
+ assert_match ' 2. a-2', out.shift
245
+ assert_match ' 3. a-3.a', out.shift
246
+ assert_match ' 4. All versions', out.shift
247
+ assert_match 'uninstalled a-1', out.shift
248
+ assert_empty out
249
+ end
250
+
195
251
  def test_execute_with_force_and_without_version_uninstalls_everything
196
252
  ui = Gem::MockGemUi.new "y\n"
197
253
 
@@ -246,7 +302,7 @@ class TestGemCommandsUninstallCommand < Gem::InstallerTestCase
246
302
  gemhome2 = "#{@gemhome}2"
247
303
 
248
304
  a_4, = util_gem 'a', 4
249
- install_gem a_4, :install_dir => gemhome2
305
+ install_gem a_4
250
306
 
251
307
  Gem::Specification.dirs = [@gemhome, gemhome2]
252
308
 
@@ -264,6 +320,29 @@ class TestGemCommandsUninstallCommand < Gem::InstallerTestCase
264
320
  assert_equal %w[default-1], Gem::Specification.all_names.sort
265
321
  end
266
322
 
323
+ def test_execute_outside_gem_home
324
+ ui = Gem::MockGemUi.new "y\n"
325
+
326
+ gemhome2 = "#{@gemhome}2"
327
+
328
+ a_4, = util_gem 'a', 4
329
+ install_gem a_4 , :install_dir => gemhome2
330
+
331
+ Gem::Specification.dirs = [@gemhome, gemhome2]
332
+
333
+ assert_includes Gem::Specification.all_names, 'a-4'
334
+
335
+ @cmd.options[:args] = ['a:4']
336
+
337
+ e = assert_raises Gem::InstallError do
338
+ use_ui ui do
339
+ @cmd.execute
340
+ end
341
+ end
342
+
343
+ assert_includes e.message, "a is not installed in GEM_HOME"
344
+ end
345
+
267
346
  def test_handle_options
268
347
  @cmd.handle_options %w[]
269
348