Skip to content
2 changes: 2 additions & 0 deletions bundler/lib/bundler/man/bundle-config.1
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ The following is a list of all configuration keys and their purpose\. You can le
.IP "\(bu" 4
\fBconsole\fR (\fBBUNDLE_CONSOLE\fR): The console that \fBbundle console\fR starts\. Defaults to \fBirb\fR\.
.IP "\(bu" 4
\fBcredential\-helper\fR (\fBBUNDLE_CREDENTIAL_HELPER\fR): The path to a credential helper to use for fetching credentials from a remote gem server\.
.IP "\(bu" 4
\fBdefault_install_uses_path\fR (\fBBUNDLE_DEFAULT_INSTALL_USES_PATH\fR): Whether a \fBbundle install\fR without an explicit \fB\-\-path\fR argument defaults to installing gems in \fB\.bundle\fR\.
.IP "\(bu" 4
\fBdeployment\fR (\fBBUNDLE_DEPLOYMENT\fR): Disallow changes to the \fBGemfile\fR\. When the \fBGemfile\fR is changed and the lockfile has not been updated, running Bundler commands will be blocked\.
Expand Down
3 changes: 3 additions & 0 deletions bundler/lib/bundler/man/bundle-config.1.ronn
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
`bundle install`.
* `console` (`BUNDLE_CONSOLE`):
The console that `bundle console` starts. Defaults to `irb`.
* `credential-helper` (`BUNDLE_CREDENTIAL_HELPER`):
The path to a credential helper to use for fetching credentials from a
remote gem server.
* `default_install_uses_path` (`BUNDLE_DEFAULT_INSTALL_USES_PATH`):
Whether a `bundle install` without an explicit `--path` argument defaults
to installing gems in `.bundle`.
Expand Down
16 changes: 15 additions & 1 deletion bundler/lib/bundler/settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ class Settings
system_bindir
trust-policy
version
credential-helper
].freeze

DEFAULT_CONFIG = {
Expand Down Expand Up @@ -197,7 +198,7 @@ def mirror_for(uri)
end

def credentials_for(uri)
self[uri.to_s] || self[uri.host]
credentials_from_helper(uri) || self[uri.to_s] || self[uri.host]
end

def gem_mirrors
Expand Down Expand Up @@ -595,5 +596,18 @@ def self.key_to_s(key)
end
end
end

def credentials_from_helper(uri)
helper_key = "credential-helper.#{uri.host}"
helper_path = self[helper_key]
return unless helper_path

begin
output = `#{helper_path}`.strip
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@segiddins do you see any security issues with calling out this? Value is coming from bundle config.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a preference that we use the same pattern as https://git-scm.com/docs/gitcredentials#_configuration_options. Additionally, we should be using IO.popen for the subprocess

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@simi @segiddins 0e48def...be95a01

Taking security concerns into consideration, I have modified the code to use IO.popen.

Additionally, similar to Git, I have implemented function a that automatically recognizes executables named bundler-credential-{helper-name} for automatically discovering helpers.

output unless output.empty?
rescue StandardError => e
Bundler.ui.warn "Credential helper failed: #{e.message}"
end
end
end
end
25 changes: 25 additions & 0 deletions bundler/spec/bundler/settings_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,31 @@
expect(settings.credentials_for(uri)).to eq(credentials)
end
end

context "with credential helper configured" do
let(:helper_path) { "/path/to/helper" }

before do
settings.set_local "credential-helper.gemserver.example.org", helper_path
end

it "uses the credential helper when configured" do
expect(settings).to receive(:`).with(helper_path).and_return("username:password\n")
expect(settings.credentials_for(uri)).to eq("username:password")
end

it "fallback to config when helper fails" do
expect(settings).to receive(:`).with(helper_path).and_raise(StandardError, "Helper failed")
expect(Bundler.ui).to receive(:warn).with("Credential helper failed: Helper failed")
settings.set_local "gemserver.example.org", "fallback:password"
expect(settings.credentials_for(uri)).to eq("fallback:password")
end

it "returns nil when helper fails and no fallback config exists" do
expect(settings).to receive(:`).with(helper_path).and_return("")
expect(settings.credentials_for(uri)).to be_nil
end
end
end

describe "URI normalization" do
Expand Down
Loading