Class: Otto::Privacy::IPPrivacy
- Inherits:
-
Object
- Object
- Otto::Privacy::IPPrivacy
- Defined in:
- lib/otto/privacy/ip_privacy.rb
Overview
All methods return UTF-8 encoded strings for Rack compatibility. See file:docs/ipaddr-encoding-quirk.md for details on IPAddr#to_s behavior.
IP address anonymization utilities
Provides methods for masking and hashing IP addresses to enhance privacy while maintaining the ability to track sessions and analyze traffic patterns.
Class Method Summary collapse
-
.hash_ip(ip, key) ⇒ String
Hash an IP address for session correlation without storing the original.
-
.mask_ip(ip, octet_precision = 1) ⇒ String
Mask an IP address by zeroing out the specified number of octets/bits.
-
.private_or_localhost?(ip) ⇒ Boolean
Check if an IP address is localhost or private (RFC 1918).
-
.valid_ip?(ip) ⇒ Boolean
Check if an IP address is valid.
Class Method Details
.hash_ip(ip, key) ⇒ String
Hash an IP address for session correlation without storing the original
Uses HMAC-SHA256 with a daily-rotating key to create a consistent identifier for the same IP within a key rotation period, but different across rotations.
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/otto/privacy/ip_privacy.rb', line 78 def self.hash_ip(ip, key) return nil if ip.nil? || ip.empty? raise ArgumentError, 'Key cannot be nil or empty' if key.nil? || key.empty? # Normalize IP address format before hashing normalized_ip = begin IPAddr.new(ip).to_s rescue IPAddr::InvalidAddressError => e raise ArgumentError, "Invalid IP address: #{ip} - #{e.}" end # Use HMAC-SHA256 for secure hashing with key OpenSSL::HMAC.hexdigest('SHA256', key, normalized_ip) end |
.mask_ip(ip, octet_precision = 1) ⇒ String
Mask an IP address by zeroing out the specified number of octets/bits
For IPv4:
-
octet_precision=1: Masks last octet (e.g., 192.168.1.100 → 192.168.1.0)
-
octet_precision=2: Masks last 2 octets (e.g., 192.168.1.100 → 192.168.0.0)
For IPv6:
-
octet_precision=1: Masks last 80 bits
-
octet_precision=2: Masks last 96 bits
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/otto/privacy/ip_privacy.rb', line 49 def self.mask_ip(ip, octet_precision = 1) return nil if ip.nil? || ip.empty? raise ArgumentError, "octet_precision must be 1 or 2, got: #{octet_precision}" unless [1, 2].include?(octet_precision) begin addr = IPAddr.new(ip) if addr.ipv4? mask_ipv4(addr, octet_precision) else mask_ipv6(addr, octet_precision) end rescue IPAddr::InvalidAddressError => e raise ArgumentError, "Invalid IP address: #{ip} - #{e.}" end end |
.private_or_localhost?(ip) ⇒ Boolean
Check if an IP address is localhost or private (RFC 1918)
Private/localhost IPs are not masked for development convenience.
113 114 115 116 117 118 119 120 |
# File 'lib/otto/privacy/ip_privacy.rb', line 113 def self.private_or_localhost?(ip) return false if ip.nil? || ip.empty? addr = IPAddr.new(ip) addr.private? || addr.loopback? rescue IPAddr::InvalidAddressError false end |
.valid_ip?(ip) ⇒ Boolean
Check if an IP address is valid
98 99 100 101 102 103 104 105 |
# File 'lib/otto/privacy/ip_privacy.rb', line 98 def self.valid_ip?(ip) return false if ip.nil? || ip.empty? IPAddr.new(ip) true rescue IPAddr::InvalidAddressError false end |