Module: Dependabot::Cargo::Helpers

Extended by:
T::Sig
Defined in:
lib/dependabot/cargo/helpers.rb

Class Method Summary collapse

Class Method Details

.cargo_command_env(dependency_files, credentials) ⇒ Object



135
136
137
138
139
140
141
142
143
144
145
# File 'lib/dependabot/cargo/helpers.rb', line 135

def self.cargo_command_env(dependency_files, credentials)
  ENV["CARGO_REGISTRY_GLOBAL_CREDENTIAL_PROVIDERS"] ||= ""
  return {} unless ENV.fetch("DEPENDABOT", nil) == "true"
  return {} unless Dependabot::Experiments.enabled?(:cargo_set_registry_token_auth)

  env = registry_token_env_from_files(dependency_files, credentials)
  Dependabot.logger.info("Setting registry token env vars: #{env.keys.join(', ')}") unless env.empty?
  env.merge!(ENV.select { |key, _value| key.match(/^CARGO_REGISTR(Y|IES)_/) })
  env["CARGO_REGISTRY_GLOBAL_CREDENTIAL_PROVIDERS"] = "cargo:token"
  env
end

.custom_registry_names(config_content, credentials) ⇒ Object



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/dependabot/cargo/helpers.rb', line 51

def self.custom_registry_names(config_content, credentials)
  parsed = TomlRB.parse(config_content)
  registries = parsed["registries"]
  return [] unless registries.is_a?(Hash)

  credential_hosts = credentials.filter_map { |cred| cred["host"] }.to_set
  credential_urls = credentials.filter_map { |cred| cred["url"]&.delete_suffix("/") }.to_set

  registries.select do |_name, config|
    config.is_a?(Hash) && registry_index_matches?(config, credential_hosts, credential_urls)
  end.keys
rescue TomlRB::Error, URI::InvalidURIError => e
  Dependabot.logger.warn("Failed to parse cargo config for registry names: #{e.message}")
  []
end

.registry_index_matches?(config, credential_hosts, credential_urls) ⇒ Boolean

Returns:

  • (Boolean)


74
75
76
77
78
79
80
81
82
83
84
# File 'lib/dependabot/cargo/helpers.rb', line 74

def self.registry_index_matches?(config, credential_hosts, credential_urls)
  index = config["index"]
  return false unless index.is_a?(String)

  # Index URLs may have a scheme prefix like "sparse+" before the actual URL
  url = index.sub(/^[a-z]+\+/, "")
  host = URI.parse(url).host

  (host && credential_hosts.include?(host)) ||
    credential_urls.include?(url.delete_suffix("/"))
end

.registry_token_env(config_content, credentials) ⇒ Object



93
94
95
96
97
98
99
100
101
# File 'lib/dependabot/cargo/helpers.rb', line 93

def self.registry_token_env(config_content, credentials)
  registry_names = custom_registry_names(config_content, credentials)
  return {} if registry_names.empty?

  registry_names.each_with_object({}) do |name, hash|
    key = "CARGO_REGISTRIES_#{name.upcase.tr('-', '_')}_TOKEN"
    hash[key] = "garbage_token"
  end
end

.registry_token_env_from_files(dependency_files, credentials) ⇒ Object



111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/dependabot/cargo/helpers.rb', line 111

def self.registry_token_env_from_files(dependency_files, credentials)
  config_files = dependency_files.select { |f| f.name.end_with?(".cargo/config.toml") }
  return {} if config_files.empty?

  # Cargo gives nearer configs precedence over ancestors on key collisions.
  # A config's `../` depth (how many parent hops appear in its name)
  # indicates how far up the tree it lives, so merge farthest-first and let
  # the nearest config (fewest `../`) win.
  ordered = config_files.sort_by { |f| -f.name.scan("../").count }
  ordered.each_with_object({}) do |config_file, env|
    env.merge!(registry_token_env(T.must(config_file.content), credentials))
  end
end

.sanitize_cargo_config(config_content, file_name: ".cargo/config.toml") ⇒ Object



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/dependabot/cargo/helpers.rb', line 23

def self.sanitize_cargo_config(config_content, file_name: ".cargo/config.toml")
  parsed = TomlRB.parse(config_content)
  return config_content unless parsed.is_a?(Hash)

  registries = parsed["registries"]
  if registries.is_a?(Hash)
    registries.each_value do |registry_config|
      registry_config.delete("credential-provider") if registry_config.is_a?(Hash)
    end
  end

  # Also strip credential-provider from [registry] (crates.io default registry). Users who `cargo publish`
  # from CI may have this set. It's a per-registry override that takes precedence over the global env var,
  # so we need to remove it to prevent Cargo from trying to look up a token.
  registry = parsed["registry"]
  registry.delete("credential-provider") if registry.is_a?(Hash)

  TomlRB.dump(parsed)
rescue TomlRB::Error => e
  raise Dependabot::DependencyFileNotParseable.new(
    file_name,
    "Failed to parse Cargo config file: #{e.message}"
  )
end