From 29a5491dd663b8daa1cacc94221456f514ed4bc0 Mon Sep 17 00:00:00 2001 From: "Brett V. Forsgren" Date: Tue, 9 Jan 2024 12:40:51 -0700 Subject: [PATCH 1/6] patch user-level `NuGet.Config` before invoking dotnet/nuget tools --- nuget/helpers/build | 5 ++ nuget/lib/dependabot/nuget/file_updater.rb | 6 +- nuget/lib/dependabot/nuget/native_helpers.rb | 11 +-- .../nuget/nuget_config_credential_helpers.rb | 70 +++++++++++++++++++ .../nuget_config_credential_helpers_spec.rb | 59 ++++++++++++++++ 5 files changed, 145 insertions(+), 6 deletions(-) create mode 100644 nuget/lib/dependabot/nuget/nuget_config_credential_helpers.rb create mode 100644 nuget/spec/dependabot/nuget/nuget_config_credential_helpers_spec.rb diff --git a/nuget/helpers/build b/nuget/helpers/build index 63eaabbdac..db7b45a2cf 100755 --- a/nuget/helpers/build +++ b/nuget/helpers/build @@ -38,3 +38,8 @@ dotnet clean echo "verifying NuGetUpdater tool" "$install_dir/NuGetUpdater/NuGetUpdater.Cli" --version + +if [ ! -f "$HOME/.nuget/NuGet/NuGet.Config" ]; then + echo "user-level NuGet.Config not found; credential patching will not work" + exit 1 +fi diff --git a/nuget/lib/dependabot/nuget/file_updater.rb b/nuget/lib/dependabot/nuget/file_updater.rb index f18a0df997..be6bdbaa33 100644 --- a/nuget/lib/dependabot/nuget/file_updater.rb +++ b/nuget/lib/dependabot/nuget/file_updater.rb @@ -62,7 +62,8 @@ def try_update_projects(dependency) next unless project_dependencies.any? { |dep| dep.name.casecmp(dependency.name).zero? } - NativeHelpers.run_nuget_updater_tool(repo_contents_path, proj_path, dependency, !dependency.top_level?) + NativeHelpers.run_nuget_updater_tool(repo_contents_path, proj_path, dependency, !dependency.top_level?, + credentials) update_ran = true end @@ -77,7 +78,8 @@ def try_update_json(dependency) project_file = project_files.first proj_path = dependency_file_path(project_file) - NativeHelpers.run_nuget_updater_tool(repo_contents_path, proj_path, dependency, !dependency.top_level?) + NativeHelpers.run_nuget_updater_tool(repo_contents_path, proj_path, dependency, !dependency.top_level?, + credentials) return true end diff --git a/nuget/lib/dependabot/nuget/native_helpers.rb b/nuget/lib/dependabot/nuget/native_helpers.rb index d734caddfe..6447fafa8c 100644 --- a/nuget/lib/dependabot/nuget/native_helpers.rb +++ b/nuget/lib/dependabot/nuget/native_helpers.rb @@ -1,6 +1,8 @@ # typed: true # frozen_string_literal: true +require_relative "nuget_config_credential_helpers.rb" + module Dependabot module Nuget module NativeHelpers @@ -46,7 +48,7 @@ def self.run_nuget_framework_check(project_tfms, package_tfms) end # rubocop:disable Metrics/MethodLength - def self.run_nuget_updater_tool(repo_root, proj_path, dependency, is_transitive) + def self.run_nuget_updater_tool(repo_root, proj_path, dependency, is_transitive, credentials) exe_path = File.join(native_helpers_root, "NuGetUpdater", "NuGetUpdater.Cli") command = [ exe_path, @@ -84,9 +86,10 @@ def self.run_nuget_updater_tool(repo_root, proj_path, dependency, is_transitive) puts "running NuGet updater:\n" + command - output = SharedHelpers.run_shell_command(command, fingerprint: fingerprint) - - puts output + NuGetConfigCredentialHelpers.patch_nuget_config_for_action(credentials, lambda { + output = SharedHelpers.run_shell_command(command, fingerprint: fingerprint) + puts output + }) end # rubocop:enable Metrics/MethodLength end diff --git a/nuget/lib/dependabot/nuget/nuget_config_credential_helpers.rb b/nuget/lib/dependabot/nuget/nuget_config_credential_helpers.rb new file mode 100644 index 0000000000..9cac1999a9 --- /dev/null +++ b/nuget/lib/dependabot/nuget/nuget_config_credential_helpers.rb @@ -0,0 +1,70 @@ +# typed: true +# frozen_string_literal: true + +module Dependabot + module Nuget + module NuGetConfigCredentialHelpers + def self.user_nuget_config_path + home_directory = Dir.home + File.join(home_directory, ".nuget", "NuGet", "NuGet.Config") + end + + def self.temporary_nuget_config_path + user_nuget_config_path + "_ORIGINAL" + end + + def self.add_credentials_to_nuget_config(credentials) + return unless File.exist?(user_nuget_config_path) + + nuget_credentials = credentials.select { |cred| cred["type"] == "nuget_feed" } + return if nuget_credentials.empty? + + File.rename(user_nuget_config_path, temporary_nuget_config_path) + + package_sources = [] + package_source_credentials = [] + nuget_credentials.each_with_index do |c, i| + source_name = "credentialed_source_#{i + 1}" + package_sources << " " + package_source_credentials << " <#{source_name}>" + package_source_credentials << " " + package_source_credentials << " " + package_source_credentials << " " + end + + nuget_config = <<~NUGET_XML + + + + + #{package_sources.join("\n")} + + + #{package_source_credentials.join("\n")} + + + NUGET_XML + File.write(user_nuget_config_path, nuget_config) + end + + def self.restore_user_nuget_config + return unless File.exist?(temporary_nuget_config_path) + + File.delete(user_nuget_config_path) + File.rename(temporary_nuget_config_path, user_nuget_config_path) + end + + # rubocop:disable Lint/SuppressedException + def self.patch_nuget_config_for_action(credentials, action) + add_credentials_to_nuget_config(credentials) + begin + action.call + rescue StandardError + ensure + restore_user_nuget_config + end + end + # rubocop:enable Lint/SuppressedException + end + end +end diff --git a/nuget/spec/dependabot/nuget/nuget_config_credential_helpers_spec.rb b/nuget/spec/dependabot/nuget/nuget_config_credential_helpers_spec.rb new file mode 100644 index 0000000000..546fbb08c4 --- /dev/null +++ b/nuget/spec/dependabot/nuget/nuget_config_credential_helpers_spec.rb @@ -0,0 +1,59 @@ +# typed: false +# frozen_string_literal: true + +require "dependabot/nuget/nuget_config_credential_helpers" + +RSpec.describe Dependabot::Nuget::NuGetConfigCredentialHelpers do + let(:user_nuget_config_contents_during_and_after_action) do + path = Dependabot::Nuget::NuGetConfigCredentialHelpers.user_nuget_config_path + content_during_action = nil + Dependabot::Nuget::NuGetConfigCredentialHelpers.patch_nuget_config_for_action(credentials, lambda { + content_during_action = File.read(path) + }) + content_after_action = File.read(path) + { content_during_action: content_during_action, content_after_action: content_after_action } + end + + subject(:result) { user_nuget_config_contents_during_and_after_action } + + describe "user level NuGet.Config patching" do + context "with an empty credential set" do + let(:credentials) { [] } + + it "does not change the contents of the file" do + expect(result[:content_during_action]).to include("https://api.nuget.org/v3/index.json") + expect(result[:content_after_action]).to include("https://api.nuget.org/v3/index.json") + end + end + + context "with non-empty credential set" do + let(:credentials) do + [{ "type" => "nuget_feed", "url" => "https://nuget.example.com/index.json", "token" => "secret_token" }] + end + + context "with credentials given" do + it "changes the content of the config file, then changes it back" do + expect(result[:content_during_action]).to include("https://nuget.example.com/index.json") + expect(result[:content_during_action]).not_to include("https://api.nuget.org/v3/index.json") + + expect(result[:content_after_action]).not_to include("https://nuget.example.com/index.json") + expect(result[:content_after_action]).to include("https://api.nuget.org/v3/index.json") + end + end + + context "when exception is raised" do + it "restores the original file after an exception" do + Dependabot::Nuget::NuGetConfigCredentialHelpers.patch_nuget_config_for_action( + credentials, + lambda { + raise "This exception was raised when the NuGet.Config file was patched" + } + ) + nuget_config_content = File.read(Dependabot::Nuget::NuGetConfigCredentialHelpers.user_nuget_config_path) + expect(nuget_config_content).not_to include("https://nuget.example.com/index.json") + expect(nuget_config_content).to include("https://api.nuget.org/v3/index.json") + end + end + end + end +end From 452c57318216743ccc8cdc3ef32de84f54a5cfe7 Mon Sep 17 00:00:00 2001 From: "Brett V. Forsgren" Date: Tue, 9 Jan 2024 13:37:00 -0700 Subject: [PATCH 2/6] use keyword arguments for helper function --- nuget/lib/dependabot/nuget/file_updater.rb | 10 ++++++---- nuget/lib/dependabot/nuget/native_helpers.rb | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/nuget/lib/dependabot/nuget/file_updater.rb b/nuget/lib/dependabot/nuget/file_updater.rb index be6bdbaa33..9ef8296041 100644 --- a/nuget/lib/dependabot/nuget/file_updater.rb +++ b/nuget/lib/dependabot/nuget/file_updater.rb @@ -62,8 +62,9 @@ def try_update_projects(dependency) next unless project_dependencies.any? { |dep| dep.name.casecmp(dependency.name).zero? } - NativeHelpers.run_nuget_updater_tool(repo_contents_path, proj_path, dependency, !dependency.top_level?, - credentials) + NativeHelpers.run_nuget_updater_tool(repo_root: repo_contents_path, proj_path: proj_path, + dependency: dependency, is_transitive: !dependency.top_level?, + credentials: credentials) update_ran = true end @@ -78,8 +79,9 @@ def try_update_json(dependency) project_file = project_files.first proj_path = dependency_file_path(project_file) - NativeHelpers.run_nuget_updater_tool(repo_contents_path, proj_path, dependency, !dependency.top_level?, - credentials) + NativeHelpers.run_nuget_updater_tool(repo_root: repo_contents_path, proj_path: proj_path, + dependency: dependency, is_transitive: !dependency.top_level?, + credentials: credentials) return true end diff --git a/nuget/lib/dependabot/nuget/native_helpers.rb b/nuget/lib/dependabot/nuget/native_helpers.rb index 6447fafa8c..d35eeb93e7 100644 --- a/nuget/lib/dependabot/nuget/native_helpers.rb +++ b/nuget/lib/dependabot/nuget/native_helpers.rb @@ -48,7 +48,7 @@ def self.run_nuget_framework_check(project_tfms, package_tfms) end # rubocop:disable Metrics/MethodLength - def self.run_nuget_updater_tool(repo_root, proj_path, dependency, is_transitive, credentials) + def self.run_nuget_updater_tool(repo_root:, proj_path:, dependency:, is_transitive:, credentials:) exe_path = File.join(native_helpers_root, "NuGetUpdater", "NuGetUpdater.Cli") command = [ exe_path, From 30981b8393abec21213dcc1e8d782495111e2388 Mon Sep 17 00:00:00 2001 From: "Brett V. Forsgren" Date: Tue, 9 Jan 2024 13:48:05 -0700 Subject: [PATCH 3/6] don't clear package feeds in default file --- nuget/lib/dependabot/nuget/nuget_config_credential_helpers.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/nuget/lib/dependabot/nuget/nuget_config_credential_helpers.rb b/nuget/lib/dependabot/nuget/nuget_config_credential_helpers.rb index 9cac1999a9..a0420e6b0c 100644 --- a/nuget/lib/dependabot/nuget/nuget_config_credential_helpers.rb +++ b/nuget/lib/dependabot/nuget/nuget_config_credential_helpers.rb @@ -36,7 +36,6 @@ def self.add_credentials_to_nuget_config(credentials) - #{package_sources.join("\n")} From f5778b53fa75acb302284b6b1ea1113c7ec20763 Mon Sep 17 00:00:00 2001 From: "Brett V. Forsgren" Date: Tue, 9 Jan 2024 17:08:54 -0700 Subject: [PATCH 4/6] use block syntax to wrap inner actions --- nuget/lib/dependabot/nuget/native_helpers.rb | 4 ++-- .../nuget/nuget_config_credential_helpers.rb | 4 ++-- .../nuget/nuget_config_credential_helpers_spec.rb | 13 +++++-------- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/nuget/lib/dependabot/nuget/native_helpers.rb b/nuget/lib/dependabot/nuget/native_helpers.rb index d35eeb93e7..a536b3a8c6 100644 --- a/nuget/lib/dependabot/nuget/native_helpers.rb +++ b/nuget/lib/dependabot/nuget/native_helpers.rb @@ -86,10 +86,10 @@ def self.run_nuget_updater_tool(repo_root:, proj_path:, dependency:, is_transiti puts "running NuGet updater:\n" + command - NuGetConfigCredentialHelpers.patch_nuget_config_for_action(credentials, lambda { + NuGetConfigCredentialHelpers.patch_nuget_config_for_action(credentials) do output = SharedHelpers.run_shell_command(command, fingerprint: fingerprint) puts output - }) + end end # rubocop:enable Metrics/MethodLength end diff --git a/nuget/lib/dependabot/nuget/nuget_config_credential_helpers.rb b/nuget/lib/dependabot/nuget/nuget_config_credential_helpers.rb index a0420e6b0c..0bfe818e56 100644 --- a/nuget/lib/dependabot/nuget/nuget_config_credential_helpers.rb +++ b/nuget/lib/dependabot/nuget/nuget_config_credential_helpers.rb @@ -54,10 +54,10 @@ def self.restore_user_nuget_config end # rubocop:disable Lint/SuppressedException - def self.patch_nuget_config_for_action(credentials, action) + def self.patch_nuget_config_for_action(credentials, &_block) add_credentials_to_nuget_config(credentials) begin - action.call + yield rescue StandardError ensure restore_user_nuget_config diff --git a/nuget/spec/dependabot/nuget/nuget_config_credential_helpers_spec.rb b/nuget/spec/dependabot/nuget/nuget_config_credential_helpers_spec.rb index 546fbb08c4..0577a789bf 100644 --- a/nuget/spec/dependabot/nuget/nuget_config_credential_helpers_spec.rb +++ b/nuget/spec/dependabot/nuget/nuget_config_credential_helpers_spec.rb @@ -7,9 +7,9 @@ let(:user_nuget_config_contents_during_and_after_action) do path = Dependabot::Nuget::NuGetConfigCredentialHelpers.user_nuget_config_path content_during_action = nil - Dependabot::Nuget::NuGetConfigCredentialHelpers.patch_nuget_config_for_action(credentials, lambda { + Dependabot::Nuget::NuGetConfigCredentialHelpers.patch_nuget_config_for_action(credentials) do content_during_action = File.read(path) - }) + end content_after_action = File.read(path) { content_during_action: content_during_action, content_after_action: content_after_action } end @@ -43,12 +43,9 @@ context "when exception is raised" do it "restores the original file after an exception" do - Dependabot::Nuget::NuGetConfigCredentialHelpers.patch_nuget_config_for_action( - credentials, - lambda { - raise "This exception was raised when the NuGet.Config file was patched" - } - ) + Dependabot::Nuget::NuGetConfigCredentialHelpers.patch_nuget_config_for_action(credentials) do + raise "This exception was raised when the NuGet.Config file was patched" + end nuget_config_content = File.read(Dependabot::Nuget::NuGetConfigCredentialHelpers.user_nuget_config_path) expect(nuget_config_content).not_to include("https://nuget.example.com/index.json") expect(nuget_config_content).to include("https://api.nuget.org/v3/index.json") From d8976907b2ccf55627901f8e2bb1eeb22f529af9 Mon Sep 17 00:00:00 2001 From: "Brett V. Forsgren" Date: Tue, 9 Jan 2024 17:22:26 -0700 Subject: [PATCH 5/6] Update nuget/lib/dependabot/nuget/native_helpers.rb Co-authored-by: Bryan Dragon <25506+bdragon@users.noreply.github.com> --- nuget/lib/dependabot/nuget/native_helpers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nuget/lib/dependabot/nuget/native_helpers.rb b/nuget/lib/dependabot/nuget/native_helpers.rb index a536b3a8c6..dd2e60ecf9 100644 --- a/nuget/lib/dependabot/nuget/native_helpers.rb +++ b/nuget/lib/dependabot/nuget/native_helpers.rb @@ -1,7 +1,7 @@ # typed: true # frozen_string_literal: true -require_relative "nuget_config_credential_helpers.rb" +require_relative "nuget_config_credential_helpers" module Dependabot module Nuget From f4586e02f15a05bdb82f2c925e120953473fcd9b Mon Sep 17 00:00:00 2001 From: "Brett V. Forsgren" Date: Wed, 10 Jan 2024 15:51:18 -0700 Subject: [PATCH 6/6] only add credentials for sources with `token` field --- .../nuget/nuget_config_credential_helpers.rb | 8 ++-- .../nuget_config_credential_helpers_spec.rb | 40 ++++++++++++++----- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/nuget/lib/dependabot/nuget/nuget_config_credential_helpers.rb b/nuget/lib/dependabot/nuget/nuget_config_credential_helpers.rb index 0bfe818e56..dfc4daa0ae 100644 --- a/nuget/lib/dependabot/nuget/nuget_config_credential_helpers.rb +++ b/nuget/lib/dependabot/nuget/nuget_config_credential_helpers.rb @@ -24,11 +24,13 @@ def self.add_credentials_to_nuget_config(credentials) package_sources = [] package_source_credentials = [] nuget_credentials.each_with_index do |c, i| - source_name = "credentialed_source_#{i + 1}" - package_sources << " " + source_name = "nuget_source_#{i + 1}" + package_sources << " " + next unless c["token"] + package_source_credentials << " <#{source_name}>" package_source_credentials << " " - package_source_credentials << " " + package_source_credentials << " " package_source_credentials << " " end diff --git a/nuget/spec/dependabot/nuget/nuget_config_credential_helpers_spec.rb b/nuget/spec/dependabot/nuget/nuget_config_credential_helpers_spec.rb index 0577a789bf..d8300827e4 100644 --- a/nuget/spec/dependabot/nuget/nuget_config_credential_helpers_spec.rb +++ b/nuget/spec/dependabot/nuget/nuget_config_credential_helpers_spec.rb @@ -15,29 +15,50 @@ end subject(:result) { user_nuget_config_contents_during_and_after_action } + let(:default_nuget_config_contents) do + File.read(Dependabot::Nuget::NuGetConfigCredentialHelpers.user_nuget_config_path) + end describe "user level NuGet.Config patching" do context "with an empty credential set" do let(:credentials) { [] } it "does not change the contents of the file" do - expect(result[:content_during_action]).to include("https://api.nuget.org/v3/index.json") - expect(result[:content_after_action]).to include("https://api.nuget.org/v3/index.json") + expect(result[:content_during_action]).to eq(default_nuget_config_contents) + expect(result[:content_after_action]).to eq(default_nuget_config_contents) end end context "with non-empty credential set" do let(:credentials) do - [{ "type" => "nuget_feed", "url" => "https://nuget.example.com/index.json", "token" => "secret_token" }] + [ + { "type" => "nuget_feed", "url" => "https://private.nuget.example.com/index.json", + "token" => "secret_token" }, + { "type" => "nuget_feed", "url" => "https://public.nuget.example.com/index.json" }, + { "type" => "not_nuget", "some_other_field" => "some other value" } + ] end context "with credentials given" do it "changes the content of the config file, then changes it back" do - expect(result[:content_during_action]).to include("https://nuget.example.com/index.json") - expect(result[:content_during_action]).not_to include("https://api.nuget.org/v3/index.json") - - expect(result[:content_after_action]).not_to include("https://nuget.example.com/index.json") - expect(result[:content_after_action]).to include("https://api.nuget.org/v3/index.json") + expect(result[:content_during_action]).to eq( + <<~XML + + + + + + + + + + + + + + XML + ) + expect(result[:content_after_action]).to eq(default_nuget_config_contents) end end @@ -47,8 +68,7 @@ raise "This exception was raised when the NuGet.Config file was patched" end nuget_config_content = File.read(Dependabot::Nuget::NuGetConfigCredentialHelpers.user_nuget_config_path) - expect(nuget_config_content).not_to include("https://nuget.example.com/index.json") - expect(nuget_config_content).to include("https://api.nuget.org/v3/index.json") + expect(nuget_config_content).to eq(default_nuget_config_contents) end end end