diff options
-rw-r--r-- | .rubocop.yml | 3 | ||||
-rw-r--r-- | lib/bundler/definition.rb | 12 | ||||
-rw-r--r-- | lib/bundler/settings.rb | 1 | ||||
-rw-r--r-- | lib/bundler/source.rb | 4 | ||||
-rw-r--r-- | lib/bundler/source/path.rb | 4 | ||||
-rw-r--r-- | lib/bundler/source/rubygems.rb | 16 | ||||
-rw-r--r-- | lib/bundler/source_list.rb | 32 | ||||
-rw-r--r-- | man/bundle-config.ronn | 3 | ||||
-rw-r--r-- | spec/install/deploy_spec.rb | 76 |
9 files changed, 140 insertions, 11 deletions
diff --git a/.rubocop.yml b/.rubocop.yml index d1241ec52f..f12289800a 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -100,6 +100,9 @@ Style/TrailingCommaInArguments: Performance/FlatMap: Enabled: false +Security/YAMLLoad: + Enabled: false + # Metrics # We've chosen to use Rubocop only for style, and not for complexity or quality checks. diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 6726cf95e8..27e7172bcc 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -407,8 +407,8 @@ module Bundler # Check if it is possible that the source is only changed thing if (new_deps.empty? && deleted_deps.empty?) && (!new_sources.empty? && !deleted_sources.empty?) - new_sources.reject! {|source| source.is_a_path? && source.path.exist? } - deleted_sources.reject! {|source| source.is_a_path? && source.path.exist? } + new_sources.reject! {|source| (source.path? && source.path.exist?) || equivalent_rubygems_remotes?(source) } + deleted_sources.reject! {|source| (source.path? && source.path.exist?) || equivalent_rubygems_remotes?(source) } end if @locked_sources != gemfile_sources @@ -636,7 +636,7 @@ module Bundler if !locked_gem_sources.empty? && !actual_remotes.empty? locked_gem_sources.each do |locked_gem| # Merge the remotes from the Gemfile into the Gemfile.lock - changes |= locked_gem.replace_remotes(actual_remotes) + changes |= locked_gem.replace_remotes(actual_remotes, Bundler.settings[:allow_deployment_source_credential_changes]) end end @@ -964,5 +964,11 @@ module Bundler requirements end.values end + + def equivalent_rubygems_remotes?(source) + return false unless source.is_a?(Source::Rubygems) + + Bundler.settings[:allow_deployment_source_credential_changes] && source.equivalent_remotes?(sources.rubygems_remotes) + end end end diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb index 15168b42e4..bd7c55e682 100644 --- a/lib/bundler/settings.rb +++ b/lib/bundler/settings.rb @@ -10,6 +10,7 @@ module Bundler BOOL_KEYS = %w[ allow_bundler_dependency_conflicts + allow_deployment_source_credential_changes allow_offline_install auto_install cache_all diff --git a/lib/bundler/source.rb b/lib/bundler/source.rb index 48f73d960e..38e001535c 100644 --- a/lib/bundler/source.rb +++ b/lib/bundler/source.rb @@ -46,6 +46,10 @@ module Bundler "#<#{self.class}:0x#{object_id} #{self}>" end + def path? + instance_of?(Bundler::Source::Path) + end + private def version_color(spec_version, locked_spec_version) diff --git a/lib/bundler/source/path.rb b/lib/bundler/source/path.rb index 310a30f1ec..806ba81935 100644 --- a/lib/bundler/source/path.rb +++ b/lib/bundler/source/path.rb @@ -116,10 +116,6 @@ module Bundler Bundler.root end - def is_a_path? - instance_of?(Path) - end - def expanded_original_path @expanded_original_path ||= expand(original_path) end diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb index 511216a5c5..70dc5ac038 100644 --- a/lib/bundler/source/rubygems.rb +++ b/lib/bundler/source/rubygems.rb @@ -219,13 +219,21 @@ module Bundler @remotes.unshift(uri) unless @remotes.include?(uri) end - def replace_remotes(other_remotes) + def equivalent_remotes?(other_remotes) + other_remotes.map(&method(:remove_auth)) == @remotes.map(&method(:remove_auth)) + end + + def replace_remotes(other_remotes, allow_equivalent = false) return false if other_remotes == @remotes + equivalent = allow_equivalent && equivalent_remotes?(other_remotes) + @remotes = [] other_remotes.reverse_each do |r| add_remote r.to_s end + + !equivalent end def unmet_deps @@ -297,7 +305,7 @@ module Bundler end def suppress_configured_credentials(remote) - remote_nouser = remote.dup.tap {|uri| uri.user = uri.password = nil }.to_s + remote_nouser = remove_auth(remote) if remote.userinfo && remote.userinfo == Bundler.settings[remote_nouser] remote_nouser else @@ -305,6 +313,10 @@ module Bundler end end + def remove_auth(remote) + remote.dup.tap {|uri| uri.user = uri.password = nil }.to_s + end + def installed_specs @installed_specs ||= Index.build do |idx| Bundler.rubygems.all_specs.reverse_each do |spec| diff --git a/lib/bundler/source_list.rb b/lib/bundler/source_list.rb index dd5a07afd7..ac2adacb3d 100644 --- a/lib/bundler/source_list.rb +++ b/lib/bundler/source_list.rb @@ -73,7 +73,7 @@ module Bundler end def get(source) - source_list_for(source).find {|s| source == s } + source_list_for(source).find {|s| equal_source?(source, s) || equivalent_source?(source, s) } end def lock_sources @@ -101,7 +101,7 @@ module Bundler replacement_sources.detect {|s| s.is_a?(Source::Rubygems) } @rubygems_aggregate = replacement_rubygems if replacement_rubygems - return true if lock_sources.to_set != replacement_sources.to_set + return true if !equal_sources?(lock_sources, replacement_sources) && !equivalent_sources?(lock_sources, replacement_sources) return true if replacement_rubygems && rubygems_remotes.to_set != replacement_rubygems.remotes.to_set false @@ -154,5 +154,33 @@ module Bundler "protocol to keep your data secure." end end + + def equal_sources?(lock_sources, replacement_sources) + lock_sources.to_set == replacement_sources.to_set + end + + def equal_source?(source, other_source) + source == other_source + end + + def equivalent_source?(source, other_source) + return false unless Bundler.settings[:allow_deployment_source_credential_changes] && source.is_a?(Source::Rubygems) + + equivalent_rubygems_sources?([source], [other_source]) + end + + def equivalent_sources?(lock_sources, replacement_sources) + return false unless Bundler.settings[:allow_deployment_source_credential_changes] + + lock_rubygems_sources, lock_other_sources = lock_sources.partition {|s| s.is_a?(Source::Rubygems) } + replacement_rubygems_sources, replacement_other_sources = replacement_sources.partition {|s| s.is_a?(Source::Rubygems) } + + equivalent_rubygems_sources?(lock_rubygems_sources, replacement_rubygems_sources) && equal_sources?(lock_other_sources, replacement_other_sources) + end + + def equivalent_rubygems_sources?(lock_sources, replacement_sources) + actual_remotes = replacement_sources.map(&:remotes).flatten.uniq + lock_sources.all? {|s| s.equivalent_remotes?(actual_remotes) } + end end end diff --git a/man/bundle-config.ronn b/man/bundle-config.ronn index dd315b9955..e4b8d596af 100644 --- a/man/bundle-config.ronn +++ b/man/bundle-config.ronn @@ -121,6 +121,9 @@ learn more about their operation in [bundle install(1)][bundle-install]. * `allow_bundler_dependency_conflicts` (`BUNDLE_ALLOW_BUNDLER_DEPENDENCY_CONFLICTS`): Allow resolving to specifications that have dependencies on `bundler` that are incompatible with the running Bundler version. +* `allow_deployment_source_credential_changes` (`BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES`): + When in deployment mode, allow changing the credentials to a gem's source. + Ex: `https://some.host.com/gems/path/` -> `https://user_name:password@some.host.com/gems/path` * `allow_offline_install` (`BUNDLE_ALLOW_OFFLINE_INSTALL`): Allow Bundler to use cached data when installing without network access. * `auto_install` (`BUNDLE_AUTO_INSTALL`): diff --git a/spec/install/deploy_spec.rb b/spec/install/deploy_spec.rb index 11cf38a577..de812b5e65 100644 --- a/spec/install/deploy_spec.rb +++ b/spec/install/deploy_spec.rb @@ -295,6 +295,82 @@ RSpec.describe "install with --deployment or --frozen" do expect(out).not_to include("You have deleted from the Gemfile") end + context "when replacing a host with the same host with credentials" do + let(:success_message) do + if Bundler::VERSION.split(".", 2).first == "1" + "Could not reach host localgemserver.test" + else + "Bundle complete!" + end + end + + before do + install_gemfile <<-G + source "http://user_name:password@localgemserver.test/" + gem "rack" + G + + lockfile <<-G + GEM + remote: http://localgemserver.test/ + specs: + rack (1.0.0) + + PLATFORMS + #{local} + + DEPENDENCIES + rack + G + end + + it "prevents the replace by default" do + bundle :install, forgotten_command_line_options(:deployment => true) + + expect(out).to match(/The list of sources changed/) + end + + context "when allow_deployment_source_credential_changes is true" do + before { bundle! "config allow_deployment_source_credential_changes true" } + + it "allows the replace" do + bundle :install, forgotten_command_line_options(:deployment => true) + + expect(out).to match(/#{success_message}/) + end + end + + context "when allow_deployment_source_credential_changes is false" do + before { bundle! "config allow_deployment_source_credential_changes false" } + + it "prevents the replace" do + bundle :install, forgotten_command_line_options(:deployment => true) + + expect(out).to match(/The list of sources changed/) + end + end + + context "when BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES env var is true" do + before { ENV["BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES"] = "true" } + + it "allows the replace" do + bundle :install, forgotten_command_line_options(:deployment => true) + + expect(out).to match(/#{success_message}/) + end + end + + context "when BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES env var is false" do + before { ENV["BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES"] = "false" } + + it "prevents the replace" do + bundle :install, forgotten_command_line_options(:deployment => true) + + expect(out).to match(/The list of sources changed/) + end + end + end + it "remembers that the bundle is frozen at runtime" do bundle! :lock |