# frozen_string_literal: true # PathRuleMatcher - Service to match Events against path_pattern Rules # # This service provides path pattern matching logic for evaluating whether # an event matches a path_pattern rule. Used for hub-side testing and validation # before agent deployment. # # Match Types: # - exact: All segments must match exactly # - prefix: Event path must start with rule segments # - suffix: Event path must end with rule segments # - contains: Rule segments must appear consecutively somewhere in event path class PathRuleMatcher def self.matches?(rule, event) return false unless rule.path_pattern_rule? return false if event.request_segment_ids.blank? rule_segments = rule.path_segment_ids event_segments = event.request_segment_ids return false if rule_segments.blank? case rule.path_match_type when 'exact' exact_match?(event_segments, rule_segments) when 'prefix' prefix_match?(event_segments, rule_segments) when 'suffix' suffix_match?(event_segments, rule_segments) when 'contains' contains_match?(event_segments, rule_segments) else false end end # Find all path_pattern rules that match the given event def self.matching_rules(event) return [] if event.request_segment_ids.blank? Rule.path_pattern_rules.active.select do |rule| matches?(rule, event) end end # Evaluate an event against path rules and return the first matching action def self.evaluate(event) matching_rule = matching_rules(event).first matching_rule&.waf_action || 'allow' end private # Exact match: all segments must match exactly # Example: [1, 2, 3] matches [1, 2, 3] only def self.exact_match?(event_segments, rule_segments) event_segments == rule_segments end # Prefix match: event path must start with rule segments # Example: rule [1, 2] matches events [1, 2], [1, 2, 3], [1, 2, 3, 4] def self.prefix_match?(event_segments, rule_segments) return false if event_segments.length < rule_segments.length event_segments[0...rule_segments.length] == rule_segments end # Suffix match: event path must end with rule segments # Example: rule [2, 3] matches events [2, 3], [1, 2, 3], [0, 1, 2, 3] def self.suffix_match?(event_segments, rule_segments) return false if event_segments.length < rule_segments.length event_segments[-rule_segments.length..-1] == rule_segments end # Contains match: rule segments must appear consecutively somewhere in event path # Example: rule [2, 3] matches [1, 2, 3, 4], [2, 3], [0, 2, 3, 5] def self.contains_match?(event_segments, rule_segments) return false if event_segments.length < rule_segments.length # Check if rule_segments appear consecutively anywhere in event_segments (0..event_segments.length - rule_segments.length).any? do |i| event_segments[i, rule_segments.length] == rule_segments end end end