127 lines
3.2 KiB
Ruby
127 lines
3.2 KiB
Ruby
# 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
|