Class: Clickwrap::IpGeolocation::TrackdownResolver

Inherits:
Resolver
  • Object
show all
Defined in:
lib/clickwrap/ip_geolocation/trackdown_resolver.rb

Overview

The optional official adapter for the trackdown gem.

bundle add trackdown --version ">= 0.4"

Clickwrap.configure do |config|
config.ip_geolocation_resolver = Clickwrap::IpGeolocation::TrackdownResolver.new
end

trackdown is NOT a dependency of this gem and must never become one. It is required lazily, inside the constructor, so that a host who never names this class never loads it — and so that a host who does name it without installing it gets one sentence telling them what to do, at the line in their initializer that asked for it, instead of a NameError somewhere in the middle of a capture.

Two rules shape the mapping below, and both exist because this adapter sits between a general-purpose geolocation gem and an evidence record that has to still be readable and honest in several years.

ONE: every field is read through respond_to?. Trackdown's result object has gained fields across releases and will gain more; a NoMethodError during a capture would roll back the protected action for a field the policy may not even have authorized. Reading defensively means a newer Trackdown supplying an accuracy radius is picked up here with no change, and an older one simply reports nil.

TWO: to_h is never persisted. Trackdown's to_h includes country_info — the whole ISO3166 country record — and a general gem is right to offer it. Copying it into evidence would store data no policy authorized and no receipt could explain. Clickwrap copies named fields, one at a time, and the extractor then keeps only the subset the server-owned policy allowed.

Pinned sources for the mapping (released tag, not a moving branch):

result object -> https://github.com/rameerez/trackdown/blob/v0.4.0/lib/trackdown/location_result.rb
Cloudflare    -> https://github.com/rameerez/trackdown/blob/v0.4.0/lib/trackdown/providers/cloudflare_provider.rb
MaxMind       -> https://github.com/rameerez/trackdown/blob/v0.4.0/lib/trackdown/providers/maxmind_provider.rb

Trackdown 0.4.0 closed the provenance gap scoped in https://github.com/rameerez/trackdown/issues/8. This adapter requires that release rather than silently manufacturing the missing facts itself.

Constant Summary collapse

PROVIDER_NAME =
"trackdown"
MINIMUM_TRACKDOWN_VERSION =
Gem::Version.new("0.4.0")
PLACEHOLDER_VALUES =

Trackdown returns the string "Unknown" for a country or city it could not determine, and Cloudflare's own "no country" code is "XX" (pinned provider source above). Neither is a place. Written into a receipt they would be indistinguishable from a country a provider actually reported, so they are mapped back to nil and the extractor records "the provider supplied no authorized field" instead. This is the single most important line in this file.

["unknown", "n/a", "xx", "-"].freeze
FIELD_READERS =

What Trackdown could supply at all, by Clickwrap field name. Read dynamically from the installed result object where possible, so a Trackdown release that adds accuracy radius starts reporting the capability without an edit here.

{
  country: %i[country_code country_name],
  region: %i[region region_name region_code],
  city: %i[city city_name],
  postal_code: %i[postal_code],
  latitude_and_longitude: %i[latitude longitude],
  timezone: %i[timezone time_zone],
  continent: %i[continent continent_code],
  metro_code: %i[metro_code],
  accuracy_radius_in_kilometers: %i[accuracy_radius accuracy_radius_in_kilometers]
}.freeze
PINNED_CAPABILITIES =

What Trackdown 0.4 can supply across its providers, used only when the result class cannot be inspected. Cloudflare does not supply an accuracy radius, while MaxMind does; with Trackdown's default :auto provider the adapter can therefore supply it even though any one lookup may not.

%i[
  country region city postal_code latitude_and_longitude timezone continent metro_code
  accuracy_radius_in_kilometers
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(provider_source: nil, source_verified_by_host: nil) ⇒ TrackdownResolver

Trust is per request in Trackdown 0.4. A host registers its verifier with Trackdown, Trackdown runs it against the same request that supplied the CDN headers, and this adapter copies the result's explicit trust state. A constructor-wide boolean would overclaim every request after one deployment assertion, so the old experimental option is refused by name.



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/clickwrap/ip_geolocation/trackdown_resolver.rb', line 91

def initialize(provider_source: nil, source_verified_by_host: nil)
  super()
  require "trackdown" unless defined?(::Trackdown)
  ensure_supported_trackdown_version!

  unless source_verified_by_host.nil?
    raise ConfigurationError,
          "TrackdownResolver no longer accepts `source_verified_by_host:`. Trackdown " \
          "0.4 verifies source trust per request. Configure " \
          "`Trackdown.configuration.verify_request_came_through_trusted_cloudflare_path_with` " \
          "or the matching CloudFront helper; the resolver will record the result's " \
          "`source_was_verified_by_host?` value."
  end

  @provider_source_fallback = (provider_source || configured_provider_source).to_s
  @capabilities = detect_capabilities.freeze

  freeze
rescue ::LoadError => error
  raise ConfigurationError,
        "Clickwrap::IpGeolocation::TrackdownResolver needs the `trackdown` gem, which is " \
        "not installed. Run `bundle add trackdown --version \">= 0.4\"` and configure " \
        "it (it needs either a " \
        "MaxMind database or Cloudflare visitor-location headers), or set " \
        "`config.ip_geolocation_resolver = nil` and turn off the IP-geolocation fields " \
        "your policies enable. The underlying load error was: #{error.message}"
end

Instance Attribute Details

#capabilitiesObject (readonly)

Returns the value of attribute capabilities.



84
85
86
# File 'lib/clickwrap/ip_geolocation/trackdown_resolver.rb', line 84

def capabilities
  @capabilities
end

Instance Method Details

#resolve(ip_address, http_request: nil) ⇒ Object



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/clickwrap/ip_geolocation/trackdown_resolver.rb', line 119

def resolve(ip_address, http_request: nil)
  address = ip_address.to_s.strip
  return unavailable("no_ip_address_to_resolve") if address.empty?

  result = ::Trackdown.locate(address, request: http_request)
  return unavailable("provider_returned_no_result") if result.nil?

  if result.respond_to?(:unavailable?) && result.unavailable?
    return unavailable(
      text(result, :unavailable_reason) || "provider_returned_unavailable",
      result:
    )
  end

  location = build_location(result)

  # Trackdown answers with a result object full of "Unknown" when no
  # provider is configured, when its database has no row for the address,
  # and when a Cloudflare country header says "XX". Once the placeholders
  # are mapped away that is an empty answer, and an empty answer is an
  # unavailable result with a reason on it — not a location whose every
  # field happens to be blank.
  return unavailable("provider_supplied_no_location_fields", result:) unless location.any_data_field?

  location
rescue StandardError => error
  # Trackdown raises for ordinary conditions — a private or loopback
  # address in development, a missing MaxMind database, a lookup timeout.
  # None of those should abort a capture by itself: the policy decides
  # whether unavailable IP geolocation is fatal. The reason carries the
  # error CLASS and never the message, because a provider message can
  # quote the IP address and this string is written to a column that a
  # redacted receipt is allowed to show.
  unavailable("trackdown_raised_#{error.class}")
end