module Gems::API::APIKeyEndpoints

The API key endpoints, including trusted publishing @api public

Public Instance Methods

Return one of the API key roles of the user the API key belongs to

@api public @authenticated true @param token [String, APIKeyRole] The token of the role, or a role. @return [APIKeyRole] the role @example

Gems.api_key_role("0123456789abcdef0123456789abcdef").api_key_permissions
# File lib/gems/api/api_key_endpoints.rb, line 110
def api_key_role(token)
  APIKeyRole.new(parse_json(get("/api/v1/oidc/api_key_roles/#{escape(token_of(token))}.json")))
end

List the API key roles of the user the API key belongs to

@api public @authenticated true @return [Array<APIKeyRole>] the roles @example

Gems.api_key_roles.map(&:name)
# File lib/gems/api/api_key_endpoints.rb, line 98
def api_key_roles
  APIKeyRole.list(parse_json(get("/api/v1/oidc/api_key_roles.json")))
end

Exchange an OIDC ID token for an API key by assuming an API key role

The role issues a key granted the permissions it was set up with when the ID token satisfies its access policy, whichever gem the key will act on, where {#exchange_trusted_publisher_token} issues one for the gems that trust the workflow the token names. The exchange is sent as that one is: without the credentials of the client, and again when the server turns it away or the network loses it.

@api public @authenticated false @param token [String, APIKeyRole] The token of the role, or a role. @param id_token [String] The OIDC ID token. @return [APIKey] the API key the role issued, including its name, scopes, and expiry @example

Gems.assume_api_key_role("0123456789abcdef0123456789abcdef", ENV.fetch("ID_TOKEN")).key
# File lib/gems/api/api_key_endpoints.rb, line 177
def assume_api_key_role(token, id_token)
  TrustedPublisherAuthenticator.new(id_token:, host:, connection:, request_builder:, redirect_handler:,
    retry_handler:, exchange_path: "/api/v1/oidc/api_key_roles/#{escape(token_of(token))}/assume_role.json")
    .exchange_token!
end

Create an API key using HTTP basic auth

The key is only returned once, so store it somewhere safe.

The endpoint answers with the key alone, so the name, scopes, expiry, and gem the key was asked for are kept in the result, as the name of a gem is kept in the versions of it that {API::VersionEndpoints#versions} returns, and the key answers {APIKey#scopes} and {APIKey#expires_at} as the keys of a token exchange do. What the endpoint answers with is left as it is.

@api public @authenticated true @param name [String] A name for the key. @param scopes [Array<Symbol, String>] The scopes to grant the key, which are among the scopes

{APIKey::SCOPES} names, such as push_rubygem and yank_rubygem; the key is granted these alone.

@param expires_at [Time, String, nil] When the key expires, as a Time or an ISO 8601 string. @param rubygem_name [String, Gem, nil] A gem to restrict the key to. @param mfa [Boolean, nil] Whether to require a one-time passcode when the key is used. @return [APIKey] the new API key @raise [ArgumentError] if a scope is not one the API defines @example

Gems.configure do |config|
  config.username = "nick@gemcutter.org"
  config.password = "schwwwwing"
end
Gems.create_api_key("ci-push", scopes: %i[push_rubygem]).key

@example

Gems.create_api_key("ci-push", scopes: %i[push_rubygem], rubygem_name: "gems", expires_at: Time.now + 86_400, mfa: true)
# File lib/gems/api/api_key_endpoints.rb, line 47
def create_api_key(name, scopes:, expires_at: nil, rubygem_name: nil, mfa: nil)
  fields = scope_fields(scopes)
  expires_at = timestamp_of(expires_at)
  rubygem_name = name_of(rubygem_name)
  settings = {expires_at:, rubygem_name:, mfa:}.compact
  requested = {"name" => name, "scopes" => granted_scopes(fields), "expires_at" => expires_at,
               "gem" => rubygem_name && {"name" => rubygem_name}}.compact
  APIKey.new(requested.merge(parse_json(post("/api/v1/api_key.json", {**fields, **settings, name:}))))
end

Exchange an OIDC ID token for an API key via trusted publishing

@api public @authenticated false @param id_token [String] The OIDC ID token. @return [APIKey] the exchanged API key, including its name, scopes, and expiry @example

Gems.exchange_trusted_publisher_token(ENV.fetch("ID_TOKEN")).key
# File lib/gems/api/api_key_endpoints.rb, line 86
def exchange_trusted_publisher_token(id_token)
  TrustedPublisherAuthenticator.new(id_token:, host:, connection:, request_builder:,
    redirect_handler:, retry_handler:).exchange_token!
end

Return one of the OIDC ID tokens the API key roles of the user have accepted

The endpoint looks the ID token up by its ID, which neither it nor {#oidc_id_tokens} answers with.

@api public @authenticated true @param id [Integer, String] The ID of the ID token. @return [OIDCIDToken] the ID token, with its claims and header @example

Gems.oidc_id_token(42).api_key_role_token
# File lib/gems/api/api_key_endpoints.rb, line 159
def oidc_id_token(id)
  OIDCIDToken.new(parse_json(get("/api/v1/oidc/id_tokens/#{escape(id)}.json")))
end

List the OIDC ID tokens the API key roles of the user have accepted

@api public @authenticated true @return [Array<OIDCIDToken>] the ID tokens, with their claims and headers @example

Gems.oidc_id_tokens.map { |id_token| id_token.jwt["claims"]["repository"] }
# File lib/gems/api/api_key_endpoints.rb, line 145
def oidc_id_tokens
  OIDCIDToken.list(parse_json(get("/api/v1/oidc/id_tokens.json")))
end

Return one of the OIDC providers whose ID tokens RubyGems.org accepts

@api public @authenticated true @param provider [Integer, String, OIDCProvider] The ID of the provider, such as the {APIKeyRole#provider_id}

of a role, or a provider.

@return [OIDCProvider] the provider @example

Gems.oidc_provider(Gems.api_key_role("0123456789abcdef0123456789abcdef").provider_id).issuer
# File lib/gems/api/api_key_endpoints.rb, line 134
def oidc_provider(provider)
  OIDCProvider.new(parse_json(get("/api/v1/oidc/providers/#{escape(id_of(provider))}.json")))
end

List the OIDC providers whose ID tokens RubyGems.org accepts

@api public @authenticated true @return [Array<OIDCProvider>] the providers @example

Gems.oidc_providers.map(&:issuer)
# File lib/gems/api/api_key_endpoints.rb, line 121
def oidc_providers
  OIDCProvider.list(parse_json(get("/api/v1/oidc/providers.json")))
end

Update the scopes of an API key using HTTP basic auth

The key is granted the scopes it is given alone, and every other scope it had is taken away.

The endpoint answers with a message rather than with the key it updated, as the endpoints of {#push}, {API::OwnerEndpoints#add_owner}, and the rest of the endpoints that act on something do, so that message is what is returned; {#create_api_key} answers with an {APIKey} because its endpoint answers with the key.

@api public @authenticated true @param key [String, APIKey] The API key to update. @param scopes [Array<Symbol, String>] The scopes to grant the key, which are among the scopes

{APIKey::SCOPES} names, such as push_rubygem and yank_rubygem.

@return [String] the message the endpoint answers with @raise [ArgumentError] if a scope is not one the API defines @example

Gems.update_api_key "rubygems_701243f217cdf23b1370c7b66b65ca97", scopes: %i[push_rubygem yank_rubygem]
# File lib/gems/api/api_key_endpoints.rb, line 74
def update_api_key(key, scopes:)
  patch("/api/v1/api_key", {**scope_fields(scopes), api_key: key_of(key)})
end