Class: Otto::Privacy::RedactedFingerprint

Inherits:
Object
  • Object
show all
Defined in:
lib/otto/privacy/redacted_fingerprint.rb

Overview

Immutable privacy-safe request fingerprint (aka CrappyFingerprint)

Contains anonymized information about a request that can be used for logging, analytics, and session tracking without storing personally identifiable information.

Examples:

Create from Rack environment

config = Otto::Privacy::Config.new
fingerprint = RedactedFingerprint.new(env, config)
fingerprint.masked_ip   # => '192.168.1.0'
fingerprint.country     # => 'US'

Constant Summary collapse

GEO_MASKED_FORWARDED_HEADERS =

IP-bearing forwarded headers overwritten with the masked IP in the geo-resolution env view. Mirrors the set IPPrivacyMiddleware#mask_forwarded_headers rewrites, so a custom resolver reading env sees masked values everywhere the middleware would. The structured RFC 7239 Forwarded header (HTTP_FORWARDED) is handled separately in #geo_env (dropped, not swapped, to keep valid syntax).

%w[
  HTTP_X_FORWARDED_FOR
  HTTP_X_REAL_IP
  HTTP_X_CLIENT_IP
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(env, config, geo_headers_trusted: true) ⇒ RedactedFingerprint

Create a new RedactedFingerprint from a Rack environment

Parameters:

  • env (Hash)

    Rack environment hash

  • config (Otto::Privacy::Config)

    Privacy configuration

  • geo_headers_trusted (Boolean) (defaults to: true)

    whether request geo headers may be trusted for this request. The middleware passes false for a non-trusted-proxy request when trusted proxies are configured, so spoofed geo headers are ignored. Defaults to true for standalone use.



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/otto/privacy/redacted_fingerprint.rb', line 48

def initialize(env, config, geo_headers_trusted: true)
  remote_ip = env['REMOTE_ADDR']

  @session_id = SecureRandom.uuid
  @timestamp = Time.now.utc
  @masked_ip = IPPrivacy.mask_ip(remote_ip, config.octet_precision)
  @hashed_ip = IPPrivacy.hash_ip(remote_ip, config.rotation_key)
  # hashed_ip is computed above from the real IP; geo resolution then runs
  # against a MASKED view — the masked IP AND an env with the IP-bearing
  # headers masked — so neither a custom resolver nor the database can see
  # the unmasked address, via the argument or via env. Country-level
  # networks are >= /24, so the /24-masked IP resolves to the same country.
  @country = if config.geo_enabled
               GeoResolver.resolve(@masked_ip, geo_env(env), config, headers_trusted: geo_headers_trusted)
             end
  @anonymized_ua = anonymize_user_agent(env['HTTP_USER_AGENT'])
  @request_path = env['PATH_INFO']
  @request_method = env['REQUEST_METHOD']
  @referer = anonymize_referer(env['HTTP_REFERER'])

  freeze
end

Instance Attribute Details

#anonymized_uaObject (readonly)

Returns the value of attribute anonymized_ua.



24
25
26
# File 'lib/otto/privacy/redacted_fingerprint.rb', line 24

def anonymized_ua
  @anonymized_ua
end

#countryObject (readonly)

Returns the value of attribute country.



24
25
26
# File 'lib/otto/privacy/redacted_fingerprint.rb', line 24

def country
  @country
end

#hashed_ipObject (readonly)

Returns the value of attribute hashed_ip.



24
25
26
# File 'lib/otto/privacy/redacted_fingerprint.rb', line 24

def hashed_ip
  @hashed_ip
end

#masked_ipObject (readonly)

Returns the value of attribute masked_ip.



24
25
26
# File 'lib/otto/privacy/redacted_fingerprint.rb', line 24

def masked_ip
  @masked_ip
end

#refererObject (readonly)

Returns the value of attribute referer.



24
25
26
# File 'lib/otto/privacy/redacted_fingerprint.rb', line 24

def referer
  @referer
end

#request_methodObject (readonly)

Returns the value of attribute request_method.



24
25
26
# File 'lib/otto/privacy/redacted_fingerprint.rb', line 24

def request_method
  @request_method
end

#request_pathObject (readonly)

Returns the value of attribute request_path.



24
25
26
# File 'lib/otto/privacy/redacted_fingerprint.rb', line 24

def request_path
  @request_path
end

#session_idObject (readonly)

Returns the value of attribute session_id.



24
25
26
# File 'lib/otto/privacy/redacted_fingerprint.rb', line 24

def session_id
  @session_id
end

#timestampObject (readonly)

Returns the value of attribute timestamp.



24
25
26
# File 'lib/otto/privacy/redacted_fingerprint.rb', line 24

def timestamp
  @timestamp
end

Instance Method Details

#inspectString

Inspect representation

Returns:

  • (String)

    Detailed representation for debugging



106
107
108
109
110
111
112
# File 'lib/otto/privacy/redacted_fingerprint.rb', line 106

def inspect
  '#<Otto::Privacy::RedactedFingerprint ' \
    "masked_ip=#{@masked_ip.inspect} " \
    "hashed_ip=#{@hashed_ip[0..15]}... " \
    "country=#{@country.inspect} " \
    "timestamp=#{@timestamp.inspect}>"
end

#to_hHash

Convert to hash for logging or serialization

Returns:

  • (Hash)

    Hash representation of fingerprint



74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/otto/privacy/redacted_fingerprint.rb', line 74

def to_h
  {
        session_id: @session_id,
         timestamp: @timestamp.iso8601,
         masked_ip: @masked_ip,
         hashed_ip: @hashed_ip,
           country: @country,
     anonymized_ua: @anonymized_ua,
    request_method: @request_method,
      request_path: @request_path,
           referer: @referer,
  }
end

#to_json(*_args) ⇒ String

Convert to JSON string

Returns:

  • (String)

    JSON representation



91
92
93
94
# File 'lib/otto/privacy/redacted_fingerprint.rb', line 91

def to_json(*_args)
  require 'json'
  to_h.to_json
end

#to_sString

String representation

Returns:

  • (String)

    Human-readable representation



99
100
101
# File 'lib/otto/privacy/redacted_fingerprint.rb', line 99

def to_s
  "#<RedactedFingerprint #{@hashed_ip[0..15]}... #{@country} #{@timestamp}>"
end