Class: Hitch::ResourceUri

Inherits:
Object
  • Object
show all
Defined in:
lib/hitch/resource_uri.rb

Defined Under Namespace

Classes: Invalid

Constant Summary collapse

LOOPBACK_HOSTS =
%w[localhost 127.0.0.1 ::1].freeze

Class Method Summary collapse

Class Method Details

.authority(uri) ⇒ Object

The host[:port] authority, with the port omitted when it is the scheme's default. URI already brackets IPv6 literals in #host and already knows each scheme's default port, so neither rule is restated here or at any call site.



38
39
40
# File 'lib/hitch/resource_uri.rb', line 38

def self.authority(uri)
  uri.port == uri.default_port ? uri.host : "#{uri.host}:#{uri.port}"
end

.canonicalize!(value, allow_loopback_http: false) ⇒ Object



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/hitch/resource_uri.rb', line 11

def self.canonicalize!(value, allow_loopback_http: false)
  uri = URI.parse(value.to_s)
  scheme = uri.scheme.to_s.downcase
  host = uri.hostname.to_s.downcase

  raise Invalid, "resource must be an absolute HTTP URI" unless uri.absolute? && host.present?
  unless uri.userinfo.nil? && !userinfo_component_present?(value)
    raise Invalid, "resource must not include userinfo"
  end
  raise Invalid, "resource must not include a fragment" unless uri.fragment.nil?
  unless scheme == "https" || (scheme == "http" && allow_loopback_http && LOOPBACK_HOSTS.include?(host))
    raise Invalid, "resource must use HTTPS"
  end

  canonical = uri.dup
  canonical.scheme = scheme
  canonical.hostname = host
  canonical.port = nil if canonical.port == canonical.default_port
  canonical.to_s
rescue URI::InvalidURIError
  raise Invalid, "resource must be an absolute HTTP URI"
end

.origin(uri) ⇒ Object

The origin (RFC 6454): scheme and authority, no path, query, or fragment. The authorization server's issuer identifier is the origin of the canonical resource_uri (RFC 8414 §2), and it MUST be byte-identical in the discovery document, the iss authorization response parameter (RFC 9207), and every bearer challenge. One derivation is what makes that true by construction rather than by convention.



48
49
50
# File 'lib/hitch/resource_uri.rb', line 48

def self.origin(uri)
  "#{uri.scheme}://#{authority(uri)}"
end

.protected_resource_metadata_url(uri) ⇒ Object

The path-aware protected-resource metadata URL (RFC 9728 §3.1): the well-known segment sits between the origin and the resource's own path. Derives its own origin so a caller cannot pair this path with a different issuer.



56
57
58
59
60
61
62
# File 'lib/hitch/resource_uri.rb', line 56

def self.(uri)
  path = uri.path.to_s
  suffix = path.empty? || path == "/" ? "" : path
  query = uri.query ? "?#{uri.query}" : ""

  "#{origin(uri)}/.well-known/oauth-protected-resource#{suffix}#{query}"
end

.userinfo_component_present?(value) ⇒ Boolean

URI.parse can miss a userinfo component that a raw scan of the authority still finds; callers use both checks together.

Returns:

  • (Boolean)


66
67
68
69
# File 'lib/hitch/resource_uri.rb', line 66

def self.userinfo_component_present?(value)
  authority = value.to_s.match(/\A[a-z][a-z0-9+.-]*:\/\/([^\/?#]*)/i)&.captures&.first
  authority&.include?("@") || false
end