Class: Gem::Guardian::RubygemsClient
- Inherits:
-
Object
- Object
- Gem::Guardian::RubygemsClient
- Defined in:
- lib/gem/guardian/rubygems_client.rb
Overview
Resolves gem sources, reads registry metadata, and downloads gem artifacts.
The client deliberately separates source discovery, checksum-provider lookup, provenance lookup, and artifact download. This lets gem-guardian support RubyGems.org, RubyGems-compatible private registries, and publisher-provided checksum URLs without coupling verification to one registry API. rubocop:disable Metrics/ClassLength
Defined Under Namespace
Classes: TrustedPublishingProvenance
Constant Summary collapse
- SOURCE_COMMIT_PATTERN =
Matches the
Source Commitfield on the RubyGems provenance page. %r{Source Commit\s+([A-Za-z0-9._/-]+@[A-Za-z0-9._-]+)}i- BUILD_FILE_PATTERN =
Matches the
Build Filefield on the RubyGems provenance page. /Build File\s+([^\s]+)/i- LOG_ENTRY_PATTERN =
Matches the transparency log URL shown on the RubyGems provenance page.
%r{transparency log entry\s*(https?://[^\s]+)}i- SHA256_PATTERN =
Matches the SHA256 checksum shown on the RubyGems provenance page.
/SHA 256 checksum\s*([a-f0-9]{64})/i- WORKFLOW_PATTERN =
Matches the provenance workflow label shown on the RubyGems provenance page.
/ Built and signed on\s+ ([A-Za-z0-9 ._-]+?) (?:\s+Build summary|\s+Source Commit|\z) /ix- DEFAULT_HOST =
Default RubyGems.org endpoint used by the client.
"https://rubygems.org"- MAX_REDIRECTS =
Maximum number of HTTP redirects followed for API and artifact requests.
5- OPEN_TIMEOUT =
Connection timeout, in seconds, for direct HTTP requests.
10- READ_TIMEOUT =
Read timeout, in seconds, for direct HTTP requests.
30
Class Method Summary collapse
-
.default_checksum_providers ⇒ Array<#checksum_for>
Built-in checksum providers used when no project configuration overrides provider order.
Instance Method Summary collapse
-
#compact_index_registry_checksum(dependency) ⇒ ChecksumProvider::Result?
Returns checksum metadata from the RubyGems/Bundler compact index.
-
#download_gem(dependency, destination) ⇒ String
Downloads the .gem file for +dependency+ into +destination+.
-
#expected_sha256(dependency) ⇒ String
Returns the expected SHA256 checksum for +dependency+.
-
#initialize(host: DEFAULT_HOST, http: Net::HTTP, credentials: Bundler.settings, spec_fetcher: Gem::SpecFetcher.fetcher, sources: Gem.sources, checksum_providers: nil) ⇒ RubygemsClient
constructor
A new instance of RubygemsClient.
-
#registry_checksum(dependency) ⇒ ChecksumProvider::Result?
Returns registry or publisher supplied checksum metadata for +dependency+.
-
#resolve_dependency(dependency) ⇒ Dependency
Returns +dependency+ with its source populated from the configured RubyGems sources.
-
#rubygems_api_checksum(dependency) ⇒ ChecksumProvider::Result?
Returns checksum metadata from the RubyGems.org-style versions API.
-
#sanitize_uri(uri) ⇒ String
Returns a sanitized URI string suitable for reports.
-
#trusted_publishing_provenance(dependency) ⇒ TrustedPublishingProvenance?
Returns trusted publishing provenance data for +dependency+ when RubyGems exposes it.
Constructor Details
#initialize(host: DEFAULT_HOST, http: Net::HTTP, credentials: Bundler.settings, spec_fetcher: Gem::SpecFetcher.fetcher, sources: Gem.sources, checksum_providers: nil) ⇒ RubygemsClient
Returns a new instance of RubygemsClient.
68 69 70 71 72 73 74 75 76 77 |
# File 'lib/gem/guardian/rubygems_client.rb', line 68 def initialize(host: DEFAULT_HOST, http: Net::HTTP, credentials: Bundler.settings, spec_fetcher: Gem::SpecFetcher.fetcher, sources: Gem.sources, checksum_providers: nil) @host = host.delete_suffix("/") @http = http @credentials = credentials @spec_fetcher = spec_fetcher @sources = sources @checksum_providers = checksum_providers || default_checksum_providers end |
Class Method Details
.default_checksum_providers ⇒ Array<#checksum_for>
Built-in checksum providers used when no project configuration overrides provider order.
57 58 59 |
# File 'lib/gem/guardian/rubygems_client.rb', line 57 def self.default_checksum_providers [ChecksumProvider::RubyGemsApi.new, ChecksumProvider::CompactIndex.new] end |
Instance Method Details
#compact_index_registry_checksum(dependency) ⇒ ChecksumProvider::Result?
Returns checksum metadata from the RubyGems/Bundler compact index.
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/gem/guardian/rubygems_client.rb', line 167 def compact_index_registry_checksum(dependency) host = host_for(dependency) info_path = "/info/#{dependency.name}" info = get(info_path, host:, progress: false) sha = compact_index_checksum_for(info, dependency) return if blank?(sha) ChecksumProvider::Result.new( sha256: sha.downcase, source: :registry, provider: "compact-index", verification_uri: "#{host.delete_suffix("/")}#{info_path}" ) rescue StandardError nil end |
#download_gem(dependency, destination) ⇒ String
Downloads the .gem file for +dependency+ into +destination+.
RubyGems is used for source/spec resolution, but gem-guardian performs the
artifact download itself. This keeps verification deterministic, applies
explicit HTTP timeouts, avoids RubyGems installer-side behavior, and prevents
Gem::Source#download from emitting progress output or hanging in internal
fetch paths.
209 210 211 212 213 214 |
# File 'lib/gem/guardian/rubygems_client.rb', line 209 def download_gem(dependency, destination) spec, source = resolve_spec_and_source(dependency) download_gem_uri(gem_uri(source, spec), destination) rescue StandardError => e raise ArtifactFetchError, "Could not fetch #{dependency.gem_filename}: #{e.}" end |
#expected_sha256(dependency) ⇒ String
Returns the expected SHA256 checksum for +dependency+.
This compatibility method returns only the digest. Prefer #registry_checksum when callers need provider metadata such as the verification URI or provider name.
108 109 110 111 112 113 114 |
# File 'lib/gem/guardian/rubygems_client.rb', line 108 def expected_sha256(dependency) checksum = registry_checksum(dependency) return checksum.sha256 if checksum raise ChecksumNotFound, "No SHA256 found for #{dependency.name} #{dependency.version} #{dependency.platform}" end |
#registry_checksum(dependency) ⇒ ChecksumProvider::Result?
Returns registry or publisher supplied checksum metadata for +dependency+.
Providers are tried in order. The first provider that returns a checksum becomes the independent checksum source. This allows RubyGems.org, compact index registries, and publisher-controlled checksum URLs to participate in the same verification flow.
125 126 127 128 129 130 131 132 133 134 |
# File 'lib/gem/guardian/rubygems_client.rb', line 125 def registry_checksum(dependency) @checksum_providers.each do |provider| checksum = provider.checksum_for(dependency, client: self) return checksum if checksum rescue StandardError next end nil end |
#resolve_dependency(dependency) ⇒ Dependency
Returns +dependency+ with its source populated from the configured RubyGems sources.
Explicit verification starts with a source-less dependency, unlike Bundler lockfile
verification where Bundler has already recorded the remote. Resolving through
RubyGems keeps gem-guardian aligned with gem install behavior for private
registries such as GitHub Packages, Gemfury, CodeArtifact, or self-hosted
RubyGems-compatible servers.
89 90 91 92 93 94 95 96 97 |
# File 'lib/gem/guardian/rubygems_client.rb', line 89 def resolve_dependency(dependency) return dependency unless blank?(dependency.source) _spec, source = resolve_spec_and_source(dependency) Dependency.new(name: dependency.name, version: dependency.version, platform: dependency.platform, source: sanitized_source_uri(source)) rescue StandardError dependency end |
#rubygems_api_checksum(dependency) ⇒ ChecksumProvider::Result?
Returns checksum metadata from the RubyGems.org-style versions API.
148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
# File 'lib/gem/guardian/rubygems_client.rb', line 148 def rubygems_api_checksum(dependency) version = matching_version(dependency) sha = version && version_checksum(version) return if blank?(sha) ChecksumProvider::Result.new( sha256: sha.downcase, source: :registry, provider: "rubygems-api", verification_uri: "#{host_for(dependency).delete_suffix("/")}/api/v1/versions/#{dependency.name}.json" ) rescue StandardError nil end |
#sanitize_uri(uri) ⇒ String
Returns a sanitized URI string suitable for reports.
140 141 142 |
# File 'lib/gem/guardian/rubygems_client.rb', line 140 def sanitize_uri(uri) sanitized_uri(uri) end |
#trusted_publishing_provenance(dependency) ⇒ TrustedPublishingProvenance?
Returns trusted publishing provenance data for +dependency+ when RubyGems exposes it.
188 189 190 191 192 193 194 195 |
# File 'lib/gem/guardian/rubygems_client.rb', line 188 def trusted_publishing_provenance(dependency) return nil unless ruby_gems_org_source?(dependency) version = matching_version(dependency) version && provenance_for(version) || attestation_api_provenance(dependency) || version_page_provenance(dependency) end |