diff --git a/common/lib/dependabot/shared_helpers.rb b/common/lib/dependabot/shared_helpers.rb index 82ea8e5fde..13d392984e 100644 --- a/common/lib/dependabot/shared_helpers.rb +++ b/common/lib/dependabot/shared_helpers.rb @@ -161,47 +161,21 @@ def self.with_git_configured(credentials:) reset_global_git_config(backup_git_config_path) end + def self.credential_helper_path + File.join(__dir__, "../../bin/git-credential-store-immutable") + end + + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/PerceivedComplexity def self.configure_git_to_use_https_with_credentials(credentials) File.open(GIT_CONFIG_GLOBAL_PATH, "w") do |file| file << "# Generated by dependabot/dependabot-core" end - configure_git_to_use_https - configure_git_credentials(credentials) - end - - def self.configure_git_to_use_https - # NOTE: we use --global here (rather than --system) so that Dependabot - # can be run without privileged access - run_shell_command( - "git config --global --replace-all url.https://github.com/."\ - "insteadOf ssh://git@github.com/" - ) - run_shell_command( - "git config --global --add url.https://github.com/."\ - "insteadOf ssh://git@github.com:" - ) - run_shell_command( - "git config --global --add url.https://github.com/."\ - "insteadOf git@github.com:" - ) - run_shell_command( - "git config --global --add url.https://github.com/."\ - "insteadOf git@github.com/" - ) - run_shell_command( - "git config --global --add url.https://github.com/."\ - "insteadOf git://github.com/" - ) - end - # rubocop:disable Metrics/PerceivedComplexity - def self.configure_git_credentials(credentials) # Then add a file-based credential store that loads a file in this repo. # Under the hood this uses git credential-store, but it's invoked through # a wrapper binary that only allows non-mutating commands. Without this, # whenever the credentials are deemed to be invalid, they're erased. - credential_helper_path = - File.join(__dir__, "../../bin/git-credential-store-immutable") run_shell_command( "git config --global credential.helper "\ "'!#{credential_helper_path} --file #{Dir.pwd}/git.store'", @@ -219,6 +193,9 @@ def self.configure_git_credentials(credentials) github_credentials.find { |c| !c["password"]&.start_with?("v1.") } || github_credentials.first + # Make sure we always have https alternatives for github.com. + configure_git_to_use_https("github.com") if github_credential.nil? + deduped_credentials = credentials - github_credentials + [github_credential].compact @@ -234,13 +211,40 @@ def self.configure_git_credentials(credentials) "@#{cred.fetch('host')}" git_store_content += authenticated_url + "\n" + configure_git_to_use_https(cred.fetch("host")) end # Save the file File.write("git.store", git_store_content) end + # rubocop:enable Metrics/AbcSize # rubocop:enable Metrics/PerceivedComplexity + def self.configure_git_to_use_https(host) + # NOTE: we use --global here (rather than --system) so that Dependabot + # can be run without privileged access + run_shell_command( + "git config --global --replace-all url.https://#{host}/."\ + "insteadOf ssh://git@#{host}/" + ) + run_shell_command( + "git config --global --add url.https://#{host}/."\ + "insteadOf ssh://git@#{host}:" + ) + run_shell_command( + "git config --global --add url.https://#{host}/."\ + "insteadOf git@#{host}:" + ) + run_shell_command( + "git config --global --add url.https://#{host}/."\ + "insteadOf git@#{host}/" + ) + run_shell_command( + "git config --global --add url.https://#{host}/."\ + "insteadOf git://#{host}/" + ) + end + def self.reset_git_repo(path) Dir.chdir(path) do run_shell_command("git reset HEAD --hard") diff --git a/common/spec/dependabot/shared_helpers_spec.rb b/common/spec/dependabot/shared_helpers_spec.rb index 650f673a58..21b5568efd 100644 --- a/common/spec/dependabot/shared_helpers_spec.rb +++ b/common/spec/dependabot/shared_helpers_spec.rb @@ -314,4 +314,138 @@ ) end end + + describe ".with_git_configured" do + config_header = "Generated by dependabot/dependabot-core" + + credentials_helper = <<~CONFIG.chomp + [credential] + helper = !#{Dependabot::SharedHelpers.credential_helper_path} --file #{Dir.pwd}/git.store + CONFIG + + def alternatives(host) + <<~CONFIG.chomp + [url "https://#{host}/"] + insteadOf = ssh://git@#{host}/ + insteadOf = ssh://git@#{host}: + insteadOf = git@#{host}: + insteadOf = git@#{host}/ + insteadOf = git://#{host}/ + CONFIG + end + + let(:credentials) { [] } + + def with_git_configured(&block) + Dependabot::SharedHelpers.with_git_configured(credentials: credentials) { block.call } + end + + let(:configured_git_config) { with_git_configured { `cat ~/.gitconfig` } } + let(:configured_git_credentials) { with_git_configured { `cat #{Dir.pwd}/git.store` } } + + context "when providing no extra credentials" do + let(:credentials) { [] } + + it "creates a .gitconfig that contains the Dependabot header" do + expect(configured_git_config).to include(config_header) + end + + it "creates a .gitconfig that contains the credentials helper" do + expect(configured_git_config).to include(credentials_helper) + end + + it "creates a .gitconfig that contains the github.com alternatives" do + expect(configured_git_config).to include(alternatives("github.com")) + end + + it "creates a git credentials store that is empty" do + expect(configured_git_credentials).to eq("") + end + end + + context "when providing github.com credentials" do + let(:credentials) do + [ + { + "type" => "git_source", + "host" => "github.com", + "username" => "x-access-token", + "password" => "fake-token" + } + ] + end + + it "creates a .gitconfig that contains the Dependabot header" do + expect(configured_git_config).to include(config_header) + end + + it "creates a .gitconfig that contains the credentials helper" do + expect(configured_git_config).to include(credentials_helper) + end + + it "creates a .gitconfig that contains the github.com alternatives" do + expect(configured_git_config).to include(alternatives("github.com")) + end + + it "creates a git credentials store that contains github.com credentials" do + expect(configured_git_credentials).to eq("https://x-access-token:fake-token@github.com\n") + end + end + + context "when providing multiple github.com credentials" do + let(:credentials) do + [ + { + "type" => "git_source", + "host" => "github.com", + "username" => "x-access-token", + "password" => "v1.fake-token" + }, + { + "type" => "git_source", + "host" => "github.com", + "username" => "x-access-token", + "password" => "fake-token" + } + ] + end + + it "creates a git credentials store that contains non-app-token github.com credentials" do + expect(configured_git_credentials).to eq("https://x-access-token:fake-token@github.com\n") + end + end + + context "when providing private git_source credentials" do + let(:credentials) do + [ + { + "type" => "git_source", + "host" => "private.com", + "username" => "x-access-token", + "password" => "fake-token" + } + ] + end + + it "creates a .gitconfig that contains the Dependabot header" do + expect(configured_git_config).to include(config_header) + end + + it "creates a .gitconfig that contains the credentials helper" do + expect(configured_git_config).to include(credentials_helper) + end + + it "creates a .gitconfig that contains the github.com alternatives" do + expect(configured_git_config).to include(alternatives("github.com")) + end + + it "creates a .gitconfig that contains the private.com alternatives" do + expect(configured_git_config).to include(alternatives("private.com")) + end + + it "creates a git credentials store that contains private git credentials" do + expect(configured_git_credentials).to eq("https://x-access-token:fake-token@private.com\n") + end + end + end end