Class: BotVerification::Service
- Inherits:
-
Object
- Object
- BotVerification::Service
- Includes:
- BotPatterns
- Defined in:
- lib/bot_verification/service.rb
Overview
Core verification service
Performance characteristics:
- Rails cache lookup (~1ms) - checks if IP was recently verified
- IP range check (~5-10ms) - database lookup with in-memory caching
- Reverse DNS (~100-2000ms) - only if IP range check fails, with strict timeout
Constant Summary collapse
- MODES =
i[search_engines search_and_ai all_known].freeze
Constants included from BotPatterns
BotPatterns::AI_BOTS_WITHOUT_VERIFICATION, BotPatterns::AI_BOTS_WITH_IP_RANGES, BotPatterns::AI_BOT_PATTERNS, BotPatterns::AI_BOT_SOURCES, BotPatterns::AI_BOT_TYPES, BotPatterns::ALL_BOTS_WITH_IP_RANGES, BotPatterns::ALL_BOT_PATTERNS, BotPatterns::IP_RANGE_SOURCES, BotPatterns::SEARCH_BOTS_WITH_DNS, BotPatterns::SEARCH_BOTS_WITH_IP_RANGES, BotPatterns::SEARCH_BOT_DNS_SUFFIXES, BotPatterns::SEARCH_BOT_PATTERNS, BotPatterns::SEARCH_BOT_TYPES, BotPatterns::SEARCH_ENGINE_SOURCES, BotPatterns::SOCIAL_BOT_PATTERNS, BotPatterns::VALID_BOT_TYPES
Class Method Summary collapse
-
.ai_bot_user_agent?(user_agent) ⇒ Boolean
Check if a user agent is a known AI bot.
-
.detect_ai_bot_type(user_agent) ⇒ Object
Detect AI bot type specifically.
-
.detect_bot_type(user_agent) ⇒ Object
Detect which bot type based on user agent (all bot types).
-
.detect_search_bot_type(user_agent) ⇒ Object
Detect search engine bot type specifically.
-
.ip_in_known_ranges?(ip, bot_type) ⇒ Boolean
Check if IP is in known IP ranges for the bot type.
-
.search_bot_user_agent?(user_agent) ⇒ Boolean
Check if a user agent is a known search engine bot.
-
.session_cache_key(ip, user_agent) ⇒ Object
Generate a cache key for session-based caching.
-
.verified_ai_bot?(ip, user_agent, strict: true) ⇒ Boolean
Check specifically if request is from a verified AI bot.
-
.verified_bot_ip?(ip, bot_type) ⇒ Boolean
Check if IP belongs to a known bot via IP ranges or DNS.
-
.verified_good_bot?(ip, user_agent, mode: :search_engines) ⇒ Boolean
Main entry point - checks if request is from a verified good bot.
-
.verify_by_reverse_dns(ip, bot_type) ⇒ Object
Verify bot by reverse DNS lookup.
-
.verify_by_reverse_dns_with_timeout(ip, bot_type) ⇒ Object
Verify bot by reverse DNS lookup with strict timeout.
Class Method Details
.ai_bot_user_agent?(user_agent) ⇒ Boolean
Check if a user agent is a known AI bot
151 152 153 154 155 |
# File 'lib/bot_verification/service.rb', line 151 def ai_bot_user_agent?(user_agent) return false if user_agent.blank? BotPatterns::AI_BOT_PATTERNS.values.any? { |pattern| user_agent.match?(pattern) } end |
.detect_ai_bot_type(user_agent) ⇒ Object
Detect AI bot type specifically
82 83 84 85 86 87 |
# File 'lib/bot_verification/service.rb', line 82 def detect_ai_bot_type(user_agent) BotPatterns::AI_BOT_PATTERNS.each do |bot_type, pattern| return bot_type if user_agent.match?(pattern) end nil end |
.detect_bot_type(user_agent) ⇒ Object
Detect which bot type based on user agent (all bot types)
72 73 74 75 76 77 78 79 |
# File 'lib/bot_verification/service.rb', line 72 def detect_bot_type(user_agent) return nil if user_agent.blank? BotPatterns::ALL_BOT_PATTERNS.each do |bot_type, pattern| return bot_type if user_agent.match?(pattern) end nil end |
.detect_search_bot_type(user_agent) ⇒ Object
Detect search engine bot type specifically
90 91 92 93 94 95 |
# File 'lib/bot_verification/service.rb', line 90 def detect_search_bot_type(user_agent) BotPatterns::SEARCH_BOT_PATTERNS.each do |bot_type, pattern| return bot_type if user_agent.match?(pattern) end nil end |
.ip_in_known_ranges?(ip, bot_type) ⇒ Boolean
Check if IP is in known IP ranges for the bot type
116 117 118 119 120 |
# File 'lib/bot_verification/service.rb', line 116 def ip_in_known_ranges?(ip, bot_type) return false unless BotPatterns::ALL_BOTS_WITH_IP_RANGES.include?(bot_type) BotVerification.ip_range_model.ip_belongs_to_bot?(ip, bot_type) end |
.search_bot_user_agent?(user_agent) ⇒ Boolean
Check if a user agent is a known search engine bot
158 159 160 161 162 |
# File 'lib/bot_verification/service.rb', line 158 def search_bot_user_agent?(user_agent) return false if user_agent.blank? BotPatterns::SEARCH_BOT_PATTERNS.values.any? { |pattern| user_agent.match?(pattern) } end |
.session_cache_key(ip, user_agent) ⇒ Object
Generate a cache key for session-based caching
165 166 167 168 |
# File 'lib/bot_verification/service.rb', line 165 def session_cache_key(ip, user_agent) fingerprint = Digest::SHA256.hexdigest("#{ip}:#{user_agent}")[0, 16] "bot_verify:#{fingerprint}" end |
.verified_ai_bot?(ip, user_agent, strict: true) ⇒ Boolean
Check specifically if request is from a verified AI bot
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/bot_verification/service.rb', line 52 def verified_ai_bot?(ip, user_agent, strict: true) return false if ip.blank? || user_agent.blank? bot_type = detect_ai_bot_type(user_agent) return false unless bot_type # AI bots without published IP ranges must be trusted by user agent # (there's no other way to verify them) if BotPatterns::AI_BOTS_WITHOUT_VERIFICATION.include?(bot_type) true elsif BotPatterns::AI_BOTS_WITH_IP_RANGES.include?(bot_type) verified_bot_ip?(ip, bot_type) elsif strict false else true end end |
.verified_bot_ip?(ip, bot_type) ⇒ Boolean
Check if IP belongs to a known bot via IP ranges or DNS
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/bot_verification/service.rb', line 98 def verified_bot_ip?(ip, bot_type) cache_key = "#{config.cache_key_prefix}:#{bot_type}:#{ip}" cached = cache.read(cache_key) return cached unless cached.nil? result = ip_in_known_ranges?(ip, bot_type) # Fall back to DNS verification if IP range check failed and DNS is enabled if result == false && !config.skip_dns_verification && BotPatterns::SEARCH_BOTS_WITH_DNS.include?(bot_type) result = verify_by_reverse_dns_with_timeout(ip, bot_type) end cache.write(cache_key, result, expires_in: config.cache_ttl) result end |
.verified_good_bot?(ip, user_agent, mode: :search_engines) ⇒ Boolean
Main entry point - checks if request is from a verified good bot
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/bot_verification/service.rb', line 26 def verified_good_bot?(ip, user_agent, mode: :search_engines) return false if ip.blank? || user_agent.blank? raise ArgumentError, "Invalid mode: #{mode}" unless MODES.include?(mode) bot_type = detect_bot_type(user_agent) return report_verification_result(nil, false, mode, ip, user_agent) unless bot_type verified = case mode when :search_engines verify_search_engine_bot(ip, bot_type) when :search_and_ai verify_search_engine_bot(ip, bot_type) || verify_ai_bot_internal(ip, bot_type) when :all_known true end report_verification_result(bot_type, verified, mode, ip, user_agent) end |
.verify_by_reverse_dns(ip, bot_type) ⇒ Object
Verify bot by reverse DNS lookup
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/bot_verification/service.rb', line 133 def verify_by_reverse_dns(ip, bot_type) suffixes = BotPatterns::SEARCH_BOT_DNS_SUFFIXES[bot_type] return false unless suffixes hostname = reverse_dns_lookup(ip) return false unless hostname valid_suffix = suffixes.any? { |suffix| hostname.end_with?(suffix) } return false unless valid_suffix forward_ips = forward_dns_lookup(hostname) forward_ips.include?(ip) rescue StandardError => e config.logger.warn("BotVerification: DNS verification failed for #{ip}: #{e.message}") false end |
.verify_by_reverse_dns_with_timeout(ip, bot_type) ⇒ Object
Verify bot by reverse DNS lookup with strict timeout
123 124 125 126 127 128 129 130 |
# File 'lib/bot_verification/service.rb', line 123 def verify_by_reverse_dns_with_timeout(ip, bot_type) Timeout.timeout(config.dns_total_timeout) do verify_by_reverse_dns(ip, bot_type) end rescue Timeout::Error config.logger.warn("BotVerification: DNS verification timed out for #{ip} (#{bot_type})") false end |