diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_fetcher.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_fetcher.rb index 4ef88467c835..ec9471de259c 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_fetcher.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_fetcher.rb @@ -10,7 +10,7 @@ module Dependabot module NpmAndYarn - class FileFetcher < Dependabot::FileFetchers::Base + class FileFetcher < Dependabot::FileFetchers::Base # rubocop:disable Metrics/ClassLength require_relative "file_fetcher/path_dependency_builder" # Npm always prefixes file paths in the lockfile "version" with "file:" @@ -22,6 +22,7 @@ class FileFetcher < Dependabot::FileFetchers::Base # "yarn link", e.g. "link:react" PATH_DEPENDENCY_STARTS = %w(file: link:. link:/ link:~/ / ./ ../ ~/).freeze PATH_DEPENDENCY_CLEAN_REGEX = /^file:|^link:/ + DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org" def self.required_files_in?(filenames) filenames.include?("package.json") @@ -85,25 +86,42 @@ def fetch_files # If every entry in the lockfile uses the same registry, we can infer # that there is a global .npmrc file, so add it here as if it were in the repo. - def inferred_npmrc + + def inferred_npmrc # rubocop:disable Metrics/PerceivedComplexity return @inferred_npmrc if defined?(@inferred_npmrc) return @inferred_npmrc = nil unless npmrc.nil? && package_lock known_registries = [] - JSON.parse(package_lock.content).fetch("dependencies", {}).each do |_name, details| - resolved = details.fetch("resolved", "https://registry.npmjs.org") + JSON.parse(package_lock.content).fetch("dependencies", {}).each do |dependency_name, details| + resolved = details.fetch("resolved", DEFAULT_NPM_REGISTRY) + + # skips values that are not strings. For example, when it is `false` as it will always fail to parse + next unless resolved.is_a?(String) + begin uri = URI.parse(resolved) rescue URI::InvalidURIError # Ignoring non-URIs since they're not registries. - # This can happen if resolved is false, for instance. next end - # Check for scheme since path dependencies will not have one - known_registries << "#{uri.scheme}://#{uri.host}" if uri.scheme && uri.host + + next unless uri.scheme && uri.host + + known_registry = "#{uri.scheme}://#{uri.host}" + path = uri.path + + next unless path + + index = path.index(dependency_name) + if index + registry_base_path = path[0...index].delete_suffix("/") + known_registry << registry_base_path + end + + known_registries << known_registry end - if known_registries.uniq.length == 1 && known_registries.first != "https://registry.npmjs.org" + if known_registries.uniq.length == 1 && known_registries.first != DEFAULT_NPM_REGISTRY Dependabot.logger.info("Inferred global NPM registry is: #{known_registries.first}") return @inferred_npmrc = Dependabot::DependencyFile.new( name: ".npmrc", diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/file_fetcher_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/file_fetcher_spec.rb index 92dcc8abe150..0ab34239f5b9 100644 --- a/npm_and_yarn/spec/dependabot/npm_and_yarn/file_fetcher_spec.rb +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/file_fetcher_spec.rb @@ -1787,7 +1787,36 @@ expect(file_fetcher_instance.files.map(&:name)). to eq(%w(package.json package-lock.json .npmrc)) expect(file_fetcher_instance.files.find { |f| f.name == ".npmrc" }.content). - to eq("registry=https://npm.fury.io") + to eq("registry=https://npm.fury.io/dependabot") + end + end + + context "with no .npmrc but package-lock.json contains a artifactory repository" do + before do + allow(file_fetcher_instance).to receive(:commit).and_return("sha") + + stub_request(:get, File.join(url, "package.json?ref=sha")). + with(headers: { "Authorization" => "token token" }). + to_return( + status: 200, + body: fixture_to_response("projects/npm6/private_artifactory_repository", "package.json"), + headers: json_header + ) + + stub_request(:get, File.join(url, "package-lock.json?ref=sha")). + with(headers: { "Authorization" => "token token" }). + to_return( + status: 200, + body: fixture_to_response("projects/npm6/private_artifactory_repository", "package-lock.json"), + headers: json_header + ) + end + + it "infers an npmrc file" do + expect(file_fetcher_instance.files.map(&:name)). + to eq(%w(package.json package-lock.json .npmrc)) + expect(file_fetcher_instance.files.find { |f| f.name == ".npmrc" }.content). + to eq("registry=https://myRegistry/api/npm/npm") end end end diff --git a/npm_and_yarn/spec/fixtures/projects/npm6/private_artifactory_repository/package-lock.json b/npm_and_yarn/spec/fixtures/projects/npm6/private_artifactory_repository/package-lock.json new file mode 100644 index 000000000000..e857c405fcd1 --- /dev/null +++ b/npm_and_yarn/spec/fixtures/projects/npm6/private_artifactory_repository/package-lock.json @@ -0,0 +1,175 @@ +{ + "name": "private_artifactory_repository", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "version": "1.0.0", + "dependencies": { + "fetch-factory": "0.0.1" + }, + "devDependencies": { + "etag": "1.0.0" + } + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://myRegistry/api/npm/npm/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/es6-promise": { + "version": "3.3.1", + "resolved": "https://myRegistry/api/npm/npm/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==" + }, + "node_modules/etag": { + "version": "1.0.0", + "resolved": "https://myRegistry/api/npm/npm/etag/-/etag-1.0.0.tgz", + "integrity": "sha512-IHsJ/pkALbMcdzbAC0hbN2dpoMFFQMP/TxEe8emE5cjanh9Lh053UdfeNyrlNW9vJaJWVoSk3mgo/MdOcNmtQQ==", + "dev": true + }, + "node_modules/fetch-factory": { + "version": "0.0.1", + "resolved": "https://myRegistry/api/npm/npm/fetch-factory/-/fetch-factory-0.0.1.tgz", + "integrity": "sha512-gexRwqIhwzDJ2pJvL0UYfiZwW06/bdYWxAmswFFts7C87CF8i6liApihTk7TZFYMDcQjvvDIvyHv0q379z0aWA==", + "dependencies": { + "es6-promise": "^3.0.2", + "isomorphic-fetch": "^2.1.1", + "lodash": "^3.10.1" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://myRegistry/api/npm/npm/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://myRegistry/api/npm/npm/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isomorphic-fetch": { + "version": "2.2.1", + "resolved": "https://myRegistry/api/npm/npm/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", + "integrity": "sha512-9c4TNAKYXM5PRyVcwUZrF3W09nQ+sO7+jydgs4ZGW9dhsLG2VOlISJABombdQqQRXCwuYG3sYV/puGf5rp0qmA==", + "dependencies": { + "node-fetch": "^1.0.1", + "whatwg-fetch": ">=0.10.0" + } + }, + "node_modules/lodash": { + "version": "3.10.1", + "resolved": "https://myRegistry/api/npm/npm/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha512-9mDDwqVIma6OZX79ZlDACZl8sBm0TEnkf99zV3iMA4GzkIT/9hiqP5mY0HoT1iNLCrKc/R1HByV+yJfRWVJryQ==" + }, + "node_modules/node-fetch": { + "version": "1.7.3", + "resolved": "https://myRegistry/api/npm/npm/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "dependencies": { + "encoding": "^0.1.11", + "is-stream": "^1.0.1" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://myRegistry/api/npm/npm/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/whatwg-fetch": { + "version": "3.6.2", + "resolved": "https://myRegistry/api/npm/npm/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", + "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" + } + }, + "dependencies": { + "encoding": { + "version": "0.1.13", + "resolved": "https://myRegistry/api/npm/npm/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "requires": { + "iconv-lite": "^0.6.2" + } + }, + "es6-promise": { + "version": "3.3.1", + "resolved": "https://myRegistry/api/npm/npm/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==" + }, + "etag": { + "version": "1.0.0", + "resolved": "https://myRegistry/api/npm/npm/etag/-/etag-1.0.0.tgz", + "integrity": "sha512-IHsJ/pkALbMcdzbAC0hbN2dpoMFFQMP/TxEe8emE5cjanh9Lh053UdfeNyrlNW9vJaJWVoSk3mgo/MdOcNmtQQ==", + "dev": true + }, + "fetch-factory": { + "version": "0.0.1", + "resolved": "https://myRegistry/api/npm/npm/fetch-factory/-/fetch-factory-0.0.1.tgz", + "integrity": "sha512-gexRwqIhwzDJ2pJvL0UYfiZwW06/bdYWxAmswFFts7C87CF8i6liApihTk7TZFYMDcQjvvDIvyHv0q379z0aWA==", + "requires": { + "es6-promise": "^3.0.2", + "isomorphic-fetch": "^2.1.1", + "lodash": "^3.10.1" + } + }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://myRegistry/api/npm/npm/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://myRegistry/api/npm/npm/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==" + }, + "isomorphic-fetch": { + "version": "2.2.1", + "resolved": "https://myRegistry/api/npm/npm/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", + "integrity": "sha512-9c4TNAKYXM5PRyVcwUZrF3W09nQ+sO7+jydgs4ZGW9dhsLG2VOlISJABombdQqQRXCwuYG3sYV/puGf5rp0qmA==", + "requires": { + "node-fetch": "^1.0.1", + "whatwg-fetch": ">=0.10.0" + } + }, + "lodash": { + "version": "3.10.1", + "resolved": "https://myRegistry/api/npm/npm/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha512-9mDDwqVIma6OZX79ZlDACZl8sBm0TEnkf99zV3iMA4GzkIT/9hiqP5mY0HoT1iNLCrKc/R1HByV+yJfRWVJryQ==" + }, + "node-fetch": { + "version": "1.7.3", + "resolved": "https://myRegistry/api/npm/npm/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "requires": { + "encoding": "^0.1.11", + "is-stream": "^1.0.1" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://myRegistry/api/npm/npm/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "whatwg-fetch": { + "version": "3.6.2", + "resolved": "https://myRegistry/api/npm/npm/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", + "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" + } + } +} diff --git a/npm_and_yarn/spec/fixtures/projects/npm6/private_artifactory_repository/package.json b/npm_and_yarn/spec/fixtures/projects/npm6/private_artifactory_repository/package.json new file mode 100644 index 000000000000..e536e736d4c2 --- /dev/null +++ b/npm_and_yarn/spec/fixtures/projects/npm6/private_artifactory_repository/package.json @@ -0,0 +1,11 @@ +{ + "version": "1.0.0", + "description": "", + "main": "index.js", + "dependencies": { + "fetch-factory": "0.0.1" + }, + "devDependencies": { + "etag": "1.0.0" + } +}