diff options
author | Homu <homu@barosl.com> | 2016-04-18 13:05:09 +0900 |
---|---|---|
committer | Homu <homu@barosl.com> | 2016-04-18 13:05:09 +0900 |
commit | 61f1c2313f5cc07fe389a2ec5d1931f8cc61b004 (patch) | |
tree | 39493b527a77e9817517cc663889561752a17984 | |
parent | 9d2676ed2b9a58ff9251ff55210747c5a07ab201 (diff) | |
parent | 4d05a21c05f7dce6f932ebbce7705fd7fbab324a (diff) | |
download | bundler-61f1c2313f5cc07fe389a2ec5d1931f8cc61b004.tar.gz |
Auto merge of #4425 - RochesterinNYC:print-sanitized-urls-for-git-sources, r=segiddins
Print sanitized urls with removed credentials for git sources on `bundle install`
If your environment includes `user1:password1` for your `GITHUB_CREDENTIALS` and you have something like the following in your `Gemfile`:
```
git "https://#{ENV['GITHUB_CREDENTIALS']}:x-oauth-basic@github.com/company/private-repo" do
gem 'be_excellent', ref: '83623'
end
```
the output for `bundle install` is:
```
bundle
Using be_excellent 0.0.1 from https://user1:password1@github.com/company/private-repo (at master@53534b2)
```
This PR sanitizes this output and removes the credentials so the output would be:
```
bundle
Using be_excellent 0.0.1 from https://github.com/company/private-repo (at master@53534b2)
```
- Also handles oauth tokens (ex. `https://oauth_token:x-oauth-basic@github.com/company/private-repo`)
Related to: bundler/bundler-features#111
-rw-r--r-- | lib/bundler.rb | 1 | ||||
-rw-r--r-- | lib/bundler/source/git/git_proxy.rb | 14 | ||||
-rw-r--r-- | lib/bundler/uri_credentials_filter.rb | 36 | ||||
-rw-r--r-- | spec/bundler/uri_credentials_filter_spec.rb | 127 | ||||
-rw-r--r-- | spec/install/gemfile/git_spec.rb | 34 |
5 files changed, 207 insertions, 5 deletions
diff --git a/lib/bundler.rb b/lib/bundler.rb index 8288f839e1..ace1e7f3c4 100644 --- a/lib/bundler.rb +++ b/lib/bundler.rb @@ -52,6 +52,7 @@ module Bundler autoload :SourceList, "bundler/source_list" autoload :RubyGemsGemInstaller, "bundler/rubygems_gem_installer" autoload :UI, "bundler/ui" + autoload :URICredentialsFilter, "bundler/uri_credentials_filter" class << self attr_writer :bundle_path diff --git a/lib/bundler/source/git/git_proxy.rb b/lib/bundler/source/git/git_proxy.rb index 19e0db86fa..9b0e95d18c 100644 --- a/lib/bundler/source/git/git_proxy.rb +++ b/lib/bundler/source/git/git_proxy.rb @@ -86,12 +86,12 @@ module Bundler def checkout if path.exist? return if has_revision_cached? - Bundler.ui.info "Fetching #{uri}" + Bundler.ui.info "Fetching #{URICredentialsFilter.credential_filtered_uri(uri)}" in_path do git_retry %(fetch --force --quiet --tags #{uri_escaped_with_configured_credentials} "refs/heads/*:refs/heads/*") end else - Bundler.ui.info "Fetching #{uri}" + Bundler.ui.info "Fetching #{URICredentialsFilter.credential_filtered_uri(uri)}" SharedHelpers.filesystem_access(path.dirname) do |p| FileUtils.mkdir_p(p) end @@ -145,10 +145,14 @@ module Bundler end def git(command, check_errors = true, error_msg = nil) - raise GitNotAllowedError.new(command) unless allow? + command_with_no_credentials = URICredentialsFilter.credential_filtered_string(command, uri) + raise GitNotAllowedError.new(command_with_no_credentials) unless allow? + out = SharedHelpers.with_clean_git_env { `git #{command}` } - raise GitCommandError.new(command, path, error_msg) if check_errors && !$?.success? - out + + stdout_with_no_credentials = URICredentialsFilter.credential_filtered_string(out, uri) + raise GitCommandError.new(command_with_no_credentials, path, error_msg) if check_errors && !$?.success? + stdout_with_no_credentials end def has_revision_cached? diff --git a/lib/bundler/uri_credentials_filter.rb b/lib/bundler/uri_credentials_filter.rb new file mode 100644 index 0000000000..997a307533 --- /dev/null +++ b/lib/bundler/uri_credentials_filter.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true +module Bundler + module URICredentialsFilter + module_function + + def credential_filtered_uri(uri_to_anonymize) + return uri_to_anonymize if uri_to_anonymize.nil? + uri = uri_to_anonymize.dup + uri = URI(uri.to_s) unless uri.is_a?(URI) + if uri.userinfo + # oauth authentication + if uri.password == "x-oauth-basic" || uri.password == "x" + # URI as string does not display with password if no user is set + oauth_designation = uri.password + uri.user = oauth_designation + end + uri.password = nil + end + return uri if uri_to_anonymize.is_a?(URI) + return uri.to_s if uri_to_anonymize.is_a?(String) + rescue URI::InvalidURIError # uri is not canonical uri scheme + uri + end + + def credential_filtered_string(str_to_filter, uri) + return str_to_filter if uri.nil? || str_to_filter.nil? + str_with_no_credentials = str_to_filter.dup + anonymous_uri_str = credential_filtered_uri(uri).to_s + uri_str = uri.to_s + if anonymous_uri_str != uri_str + str_with_no_credentials = str_with_no_credentials.gsub(uri_str, anonymous_uri_str) + end + str_with_no_credentials + end + end +end diff --git a/spec/bundler/uri_credentials_filter_spec.rb b/spec/bundler/uri_credentials_filter_spec.rb new file mode 100644 index 0000000000..b890c0ce5f --- /dev/null +++ b/spec/bundler/uri_credentials_filter_spec.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true +require "spec_helper" + +describe Bundler::URICredentialsFilter do + subject { described_class } + + describe "#credential_filtered_uri" do + shared_examples_for "original type of uri is maintained" do + it "maintains same type for return value as uri input type" do + expect(subject.credential_filtered_uri(uri)).to be_kind_of(uri.class) + end + end + + shared_examples_for "sensitive credentials in uri are filtered out" do + context "authentication using oauth credentials" do + context "specified via 'x-oauth-basic'" do + let(:credentials) { "oauth_token:x-oauth-basic@" } + + it "returns the uri without the oauth token" do + expect(subject.credential_filtered_uri(uri).to_s).to eq(URI("https://x-oauth-basic@github.com/company/private-repo").to_s) + end + + it_behaves_like "original type of uri is maintained" + end + + context "specified via 'x'" do + let(:credentials) { "oauth_token:x@" } + + it "returns the uri without the oauth token" do + expect(subject.credential_filtered_uri(uri).to_s).to eq(URI("https://x@github.com/company/private-repo").to_s) + end + + it_behaves_like "original type of uri is maintained" + end + end + + context "authentication using login credentials" do + let(:credentials) { "username1:hunter3@" } + + it "returns the uri without the password" do + expect(subject.credential_filtered_uri(uri).to_s).to eq(URI("https://username1@github.com/company/private-repo").to_s) + end + + it_behaves_like "original type of uri is maintained" + end + + context "authentication without credentials" do + let(:credentials) { "" } + + it "returns the same uri" do + expect(subject.credential_filtered_uri(uri).to_s).to eq(uri.to_s) + end + + it_behaves_like "original type of uri is maintained" + end + end + + context "uri is a uri object" do + let(:uri) { URI("https://#{credentials}github.com/company/private-repo") } + + it_behaves_like "sensitive credentials in uri are filtered out" + end + + context "uri is a uri string" do + let(:uri) { "https://#{credentials}github.com/company/private-repo" } + + it_behaves_like "sensitive credentials in uri are filtered out" + end + + context "uri is a non-uri format string (ex. path)" do + let(:uri) { "/path/to/repo" } + + it "returns the same uri" do + expect(subject.credential_filtered_uri(uri).to_s).to eq(uri.to_s) + end + + it_behaves_like "original type of uri is maintained" + end + + context "uri is nil" do + let(:uri) { nil } + + it "returns nil" do + expect(subject.credential_filtered_uri(uri)).to be_nil + end + + it_behaves_like "original type of uri is maintained" + end + end + + describe "#credential_filtered_string" do + let(:str_to_filter) { "This is a git message containing a uri #{uri}!" } + let(:credentials) { "" } + let(:uri) { URI("https://#{credentials}github.com/company/private-repo") } + + context "with a uri that contains credentials" do + let(:credentials) { "oauth_token:x-oauth-basic@" } + + it "returns the string without the sensitive credentials" do + expect(subject.credential_filtered_string(str_to_filter, uri)).to eq( + "This is a git message containing a uri https://x-oauth-basic@github.com/company/private-repo!") + end + end + + context "that does not contains credentials" do + it "returns the same string" do + expect(subject.credential_filtered_string(str_to_filter, uri)).to eq(str_to_filter) + end + end + + context "string to filter is nil" do + let(:str_to_filter) { nil } + + it "returns nil" do + expect(subject.credential_filtered_string(str_to_filter, uri)).to be_nil + end + end + + context "uri to filter out is nil" do + let(:uri) { nil } + + it "returns the same string" do + expect(subject.credential_filtered_string(str_to_filter, uri)).to eq(str_to_filter) + end + end + end +end diff --git a/spec/install/gemfile/git_spec.rb b/spec/install/gemfile/git_spec.rb index 5081d35fa7..2cb62b424e 100644 --- a/spec/install/gemfile/git_spec.rb +++ b/spec/install/gemfile/git_spec.rb @@ -1102,4 +1102,38 @@ describe "bundle install with git sources" do end end end + + context "git sources that include credentials" do + context "that are username and password" do + let(:credentials) { "user1:password1" } + + it "does not display the password" do + install_gemfile <<-G, :expect_err => true + git "https://#{credentials}@github.com/company/private-repo" do + gem "foo" + end + G + + bundle :install, :expect_err => true + expect(out).to_not include("password1") + expect(out).to include("Fetching https://user1@github.com/company/private-repo") + end + end + + context "that is an oauth token" do + let(:credentials) { "oauth_token" } + + it "displays the oauth scheme but not the oauth token" do + install_gemfile <<-G, :expect_err => true + git "https://#{credentials}:x-oauth-basic@github.com/company/private-repo" do + gem "foo" + end + G + + bundle :install, :expect_err => true + expect(out).to_not include("oauth_token") + expect(out).to include("Fetching https://x-oauth-basic@github.com/company/private-repo") + end + end + end end |