summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.rubocop.yml3
-rw-r--r--lib/bundler/definition.rb12
-rw-r--r--lib/bundler/settings.rb1
-rw-r--r--lib/bundler/source.rb4
-rw-r--r--lib/bundler/source/path.rb4
-rw-r--r--lib/bundler/source/rubygems.rb16
-rw-r--r--lib/bundler/source_list.rb32
-rw-r--r--man/bundle-config.ronn3
-rw-r--r--spec/install/deploy_spec.rb76
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