First commit!

This commit is contained in:
Dan Milne
2025-11-03 17:37:28 +11:00
commit 429d41eead
141 changed files with 5890 additions and 0 deletions

126
app/models/rule.rb Normal file
View File

@@ -0,0 +1,126 @@
# frozen_string_literal: true
class Rule < ApplicationRecord
belongs_to :rule_set
validates :rule_type, presence: true, inclusion: { in: RuleSet::RULE_TYPES }
validates :target, presence: true
validates :action, presence: true, inclusion: { in: RuleSet::ACTIONS }
validates :priority, presence: true, numericality: { greater_than: 0 }
scope :enabled, -> { where(enabled: true) }
scope :by_priority, -> { order(priority: :desc, created_at: :desc) }
scope :expired, -> { where("expires_at < ?", Time.current) }
scope :not_expired, -> { where("expires_at IS NULL OR expires_at > ?", Time.current) }
# Check if rule is currently active
def active?
enabled? && (expires_at.nil? || expires_at > Time.current)
end
# Check if rule matches given request context
def matches?(context)
return false unless active?
case rule_type
when 'ip'
match_ip_rule?(context)
when 'cidr'
match_cidr_rule?(context)
when 'path'
match_path_rule?(context)
when 'user_agent'
match_user_agent_rule?(context)
when 'parameter'
match_parameter_rule?(context)
when 'method'
match_method_rule?(context)
when 'country'
match_country_rule?(context)
else
false
end
end
def to_waf_format
{
id: id,
type: rule_type,
target: target,
action: action,
conditions: conditions || {},
priority: priority,
expires_at: expires_at,
active: active?
}
end
private
def match_ip_rule?(context)
return false unless context[:ip_address]
target == context[:ip_address]
end
def match_cidr_rule?(context)
return false unless context[:ip_address]
begin
range = IPAddr.new(target)
range.include?(context[:ip_address])
rescue IPAddr::InvalidAddressError
false
end
end
def match_path_rule?(context)
return false unless context[:request_path]
# Support exact match and regex patterns
if conditions&.dig('regex') == true
Regexp.new(target).match?(context[:request_path])
else
context[:request_path].start_with?(target)
end
end
def match_user_agent_rule?(context)
return false unless context[:user_agent]
# Support exact match and regex patterns
if conditions&.dig('regex') == true
Regexp.new(target, Regexp::IGNORECASE).match?(context[:user_agent])
else
context[:user_agent].downcase.include?(target.downcase)
end
end
def match_parameter_rule?(context)
return false unless context[:query_params]
param_name = conditions&.dig('parameter_name') || target
param_value = context[:query_params][param_name]
return false unless param_value
# Check if parameter value matches pattern
if conditions&.dig('regex') == true
Regexp.new(target, Regexp::IGNORECASE).match?(param_value.to_s)
else
param_value.to_s.downcase.include?(target.downcase)
end
end
def match_method_rule?(context)
return false unless context[:request_method]
target.upcase == context[:request_method].upcase
end
def match_country_rule?(context)
return false unless context[:country_code]
target.upcase == context[:country_code].upcase
end
end