class Gems::TrustedPublisherAuthenticator

Authenticator for trusted publishing

Exchanges an OIDC ID token for a RubyGems API key on first use and then authenticates requests with that key.

The ID token and the host it is exchanged with are what an authenticator is built with, beside the connection, request builder, redirect handler, and retry handler it sends the exchange with, which default to ones built for the authenticator: a client hands it the ones it makes its own requests with, so that the exchange is sent with the timeouts, proxy, and retries the client was configured with. The ID token is given to a client with the ‘id_token` option, and exchanged by hand with {API::APIKeyEndpoints#exchange_trusted_publisher_token}.

@api private

Attributes

The connection used for the token exchange @api private @return [Connection] the connection @example Get the connection

authenticator.connection

The path of the endpoint the ID token is exchanged with @api private @return [String] the path, which is the trusted publishing exchange unless an API key role is assumed @example Get the path of the exchange

authenticator.exchange_path

The host to exchange the token with @api private @return [String] the host, including scheme @example Get the host

authenticator.host

The OIDC ID token @api private @return [String] the OIDC ID token @example Get the ID token

authenticator.id_token

The redirect handler the token exchange is followed with @api private @return [RedirectHandler] the redirect handler @example Get the redirect handler

authenticator.redirect_handler

The request builder used for the token exchange @api private @return [RequestBuilder] the request builder @example Get the request builder

authenticator.request_builder

The retry handler the token exchange is sent again with @api private @return [RetryHandler] the retry handler @example Get the retry handler

authenticator.retry_handler

Public Class Methods

Initialize a new TrustedPublisherAuthenticator

The four that follow the ID token and the host are what the exchange is sent with, and default to ones built for the authenticator (see the class).

@api private @param id_token [String] the OIDC ID token @param host [String] the host to exchange the token with, including scheme @param connection [Connection] the connection used for the token exchange @param request_builder [RequestBuilder] the request builder used for the token exchange @param redirect_handler [RedirectHandler] the redirect handler the exchange is followed with @param retry_handler [RetryHandler] the retry handler the exchange is sent again with @param exchange_path [String] the path of the endpoint the token is exchanged with, which is the trusted

publishing exchange unless the token assumes an API key role

@return [TrustedPublisherAuthenticator] a new instance @example Create a trusted publisher authenticator

authenticator = Gems::TrustedPublisherAuthenticator.new(id_token: ENV.fetch("ID_TOKEN"))
# File lib/gems/trusted_publisher_authenticator.rb, line 101
def initialize(id_token:, host: Gems.default_host, connection: Connection.new,
  request_builder: RequestBuilder.new,
  redirect_handler: RedirectHandler.new(connection:, request_builder:),
  retry_handler: RetryHandler.new, exchange_path: EXCHANGE_TOKEN_PATH)
  @id_token = id_token
  @exchange_path = exchange_path
  @host = host
  @connection = connection
  @request_builder = request_builder
  @redirect_handler = redirect_handler
  @retry_handler = retry_handler
  @mutex = Mutex.new
end

Public Instance Methods

The API key obtained from the token exchange

@api private @return [String, nil] the API key, or nil before the token has been exchanged @example Get the exchanged API key

authenticator.api_key
# File lib/gems/trusted_publisher_authenticator.rb, line 137
def api_key
  @exchanged_key&.key
end

Exchange the OIDC ID token for a RubyGems API key

The redirects of the exchange are followed as those of every other request are, so that a host that answers the endpoint with one is exchanged with rather than raising the {HTTPError} of the redirect. A redirect to another scheme, host, or port is followed without the ‘Accept` header of the exchange, as it is for a request of the client, and with the body only when the redirect preserves the method.

The token is exchanged once, under a lock, and the key it was exchanged for is returned from then on, since RubyGems.org refuses to exchange a token again: a caller exchanging the token itself while a request is being authenticated is given the key the request was authenticated with, whichever of them took the lock first.

@api private @return [APIKey] the exchanged API key, including its name, scopes, and expiry @raise [HTTPError] if the token exchange fails @example Exchange the ID token

authenticator.exchange_token!.expires_at
# File lib/gems/trusted_publisher_authenticator.rb, line 167
def exchange_token!
  @mutex.synchronize { @exchanged_key ||= exchange! }
end

Generate the authentication headers for a request

Exchanges the ID token for an API key on first use, once even when requests are made concurrently, since RubyGems.org issues the key only once per token.

@api private @param _request [Net::HTTPRequest] the HTTP request @return [Hash{String => String}] the authentication headers with the exchanged API key @raise [HTTPError] if the token exchange fails @raise [InvalidResponse] if the response is not JSON @example Generate an authentication header

authenticator.header(request)
# File lib/gems/trusted_publisher_authenticator.rb, line 127
def header(_request)
  {AUTHENTICATION_HEADER => exchange_token!.key}
end

Summarize the authenticator for the console

@api private @return [String] the summary, which includes the host but not the ID token or API key @example Inspect a trusted publisher authenticator

authenticator.inspect # => #<Gems::TrustedPublisherAuthenticator host="https://rubygems.org">
# File lib/gems/trusted_publisher_authenticator.rb, line 147
def inspect
  "#<#{self.class} host=#{host.inspect}>"
end