Class: Basecamp::Oauth::Discovery

Inherits:
Object
  • Object
show all
Defined in:
lib/basecamp/oauth/discovery.rb

Overview

Fetches RFC 8414 OAuth 2 Authorization Server Metadata and binds the returned issuer to the requested issuer origin (hop 2 of resource-first discovery).

Defined Under Namespace

Classes: IssuerBindingError

Instance Method Summary collapse

Constructor Details

#initialize(http_client: nil, timeout: 10, max_body_bytes: Fetcher::DEFAULT_MAX_BODY_BYTES) ⇒ Discovery

Returns a new instance of Discovery.

Parameters:

  • http_client (Faraday::Connection, nil) (defaults to: nil)

    injected Faraday client (kept, verified redirect-free); nil selects the default headers-first Net::HTTP transport (Fetcher.stream_http)

  • timeout (Numeric) (defaults to: 10)

    request timeout in seconds (default: 10); an invalid value falls back via Fetcher.normalize_timeout

  • max_body_bytes (Integer) (defaults to: Fetcher::DEFAULT_MAX_BODY_BYTES)

    bounded read cap in bytes



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/basecamp/oauth/discovery.rb', line 27

def initialize(http_client: nil, timeout: 10, max_body_bytes: Fetcher::DEFAULT_MAX_BODY_BYTES)
  Fetcher.ensure_redirects_suppressed!(http_client) if http_client
  # Normalize before storing (nil selects the headers-first Net::HTTP
  # transport; an injected client is kept) and before the fetch computes its
  # wall-clock deadline: a non-finite/non-positive timeout must not disable
  # either bound (see Fetcher.normalize_timeout).
  @timeout = Fetcher.normalize_timeout(timeout)
  # nil selects the headers-first {Fetcher.stream_http} transport (total
  # wall-clock bound incl. the header phase); an injected connection keeps
  # the Faraday path, verified redirect-free above.
  @http_client = http_client
  # Normalize the public cap to a finite non-negative Integer: a nil, float,
  # or Float::INFINITY would otherwise disable the streaming memory bound
  # (an infinite/undefined cap never trips +total > max_body_bytes+),
  # reintroducing an SSRF/OOM risk. Shared with Resource and the device flow.
  @max_body_bytes = Fetcher.normalize_body_cap(max_body_bytes)
end

Instance Method Details

#discover(base_url) ⇒ Config

Discovers OAuth configuration from {base_url}/.well-known/oauth-authorization-server, binding the returned issuer to base_url by code-point (RFC 8414 §3.3/§4, no normalization beyond origin-root parsing). token_endpoint is required; authorization_endpoint is optional (device-only servers omit it).

Examples:

config = Basecamp::Oauth::Discovery.new.discover("https://launchpad.37signals.com")
config.token_endpoint # => "https://launchpad.37signals.com/authorization/token"

Parameters:

  • base_url (String)

    the OAuth server's issuer origin

Returns:

  • (Config)

    the OAuth server configuration

Raises:



59
60
61
62
63
64
# File 'lib/basecamp/oauth/discovery.rb', line 59

def discover(base_url)
  issuer_origin = Basecamp::Security.require_origin_root!(base_url, "OAuth discovery base URL")
  # Bind against the caller's raw +base_url+ (RFC 8414 §3.3, SPEC.md §16 "NO
  # normalization"); the normalized origin is only for the fetch URL.
  discover_and_bind(issuer_origin, base_url)
end

#discover_advertised(advertised_issuer) ⇒ Object

Resource-first entry: validate advertised_issuer as an origin root, then fetch from the normalized origin and bind the AS metadata issuer against the RAW advertised string by code-point. Routing and binding are distinct — an AS whose issuer matches what the resource advertised (e.g. a trailing slash or explicit default port) binds instead of being normalized away into a false issuer_mismatch. Validation happens HERE so there is NO public unvalidated-fetch entry point: the SSRF origin policy is always enforced.

Parameters:

  • advertised_issuer (String)

    the raw issuer string a resource advertised

Raises:



76
77
78
79
# File 'lib/basecamp/oauth/discovery.rb', line 76

def discover_advertised(advertised_issuer)
  issuer_origin = Basecamp::Security.require_origin_root!(advertised_issuer, "advertised issuer")
  discover_and_bind(issuer_origin, advertised_issuer)
end