wafris 2.0.3 → 2.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/wafris/configuration.rb +29 -71
- data/lib/wafris/log_suppressor.rb +4 -4
- data/lib/wafris/middleware.rb +6 -18
- data/lib/wafris/proxy_filter.rb +2 -2
- data/lib/wafris/railtie.rb +1 -1
- data/lib/wafris/version.rb +1 -1
- data/lib/wafris/wafris_request.rb +25 -7
- data/lib/wafris.rb +213 -287
- metadata +36 -8
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: d1a6832a6803974c5ebc7e337c3e118fb876dececdb8154f9877411e4169baca
         | 
| 4 | 
            +
              data.tar.gz: f8c60d439b09b2b299bb17f6ab233fb265b214e72ea1791cc43d5c8a34aa2264
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 020ff0f8179cfd43719fc7e44814fb734873f7f496e6c8a5498800643f06bb9fc1914f255cbbbb4b5b96f7a91d2fe20d6e5c9f26ed8b623684823818e9a6d970
         | 
| 7 | 
            +
              data.tar.gz: d9e6a41ea3ab39f34fbf74a81cea82846b3ad2861939c06f9ec3cb6a5f6f95e198c1af3e83c5af7ed3cffb53313398e852f163489fc4b07dc595bc5f6b4d94d3
         | 
    
        data/lib/wafris/configuration.rb
    CHANGED
    
    | @@ -1,9 +1,7 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
            require_relative 'version'
         | 
| 1 | 
            +
            require_relative "version"
         | 
| 3 2 |  | 
| 4 3 | 
             
            module Wafris
         | 
| 5 4 | 
             
              class Configuration
         | 
| 6 | 
            -
             | 
| 7 5 | 
             
                attr_accessor :api_key
         | 
| 8 6 | 
             
                attr_accessor :db_file_path
         | 
| 9 7 | 
             
                attr_accessor :db_file_name
         | 
| @@ -21,23 +19,17 @@ module Wafris | |
| 21 19 | 
             
                attr_accessor :rate_limiters
         | 
| 22 20 |  | 
| 23 21 | 
             
                def initialize
         | 
| 24 | 
            -
             | 
| 25 22 | 
             
                  # API Key - Required
         | 
| 26 | 
            -
                  if ENV[ | 
| 27 | 
            -
                    @api_key = ENV[ | 
| 23 | 
            +
                  if ENV["WAFRIS_API_KEY"]
         | 
| 24 | 
            +
                    @api_key = ENV["WAFRIS_API_KEY"]
         | 
| 28 25 | 
             
                  else
         | 
| 29 | 
            -
                    unless @api_key | 
| 30 | 
            -
                      LogSuppressor.puts_log("Firewall disabled as neither local only or API key set") | 
| 26 | 
            +
                    unless @api_key
         | 
| 27 | 
            +
                      LogSuppressor.puts_log("Firewall disabled as neither local only or API key set")
         | 
| 31 28 | 
             
                    end
         | 
| 32 29 | 
             
                  end
         | 
| 33 30 |  | 
| 34 31 | 
             
                  # DB FILE PATH LOCATION - Optional
         | 
| 35 | 
            -
                   | 
| 36 | 
            -
                    @db_file_path = ENV['WAFRIS_DB_FILE_PATH']      
         | 
| 37 | 
            -
                  else
         | 
| 38 | 
            -
                    #@db_file_path = Rails.root.join('tmp', 'wafris').to_s
         | 
| 39 | 
            -
                    @db_file_path = './tmp/wafris'
         | 
| 40 | 
            -
                  end
         | 
| 32 | 
            +
                  @db_file_path = ENV["WAFRIS_DB_FILE_PATH"] || "./tmp/wafris"
         | 
| 41 33 |  | 
| 42 34 | 
             
                  # Ensure that the db_file_path exists
         | 
| 43 35 | 
             
                  unless File.directory?(@db_file_path)
         | 
| @@ -46,95 +38,61 @@ module Wafris | |
| 46 38 | 
             
                  end
         | 
| 47 39 |  | 
| 48 40 | 
             
                  # DB FILE NAME - For local
         | 
| 49 | 
            -
                   | 
| 50 | 
            -
             | 
| 51 | 
            -
                  else
         | 
| 52 | 
            -
                    @db_file_name = 'wafris.db'
         | 
| 53 | 
            -
                  end
         | 
| 54 | 
            -
              
         | 
| 41 | 
            +
                  @db_file_name = ENV["WAFRIS_DB_FILE_NAME"] || "wafris.db"
         | 
| 42 | 
            +
             | 
| 55 43 | 
             
                  # DOWNSYNC
         | 
| 56 44 | 
             
                  # Custom Rules are checked often (default 1 minute) - Optional
         | 
| 57 | 
            -
                   | 
| 58 | 
            -
             | 
| 59 | 
            -
                  else
         | 
| 60 | 
            -
                    @downsync_custom_rules_interval = 60
         | 
| 61 | 
            -
                  end
         | 
| 62 | 
            -
              
         | 
| 45 | 
            +
                  @downsync_custom_rules_interval = ENV["WAFRIS_DOWNSYNC_CUSTOM_RULES_INTERVAL"]&.to_i || 60
         | 
| 46 | 
            +
             | 
| 63 47 | 
             
                  # Data Subscriptions are checked rarely (default 1 day) - Optional
         | 
| 64 | 
            -
                   | 
| 65 | 
            -
             | 
| 66 | 
            -
                  else
         | 
| 67 | 
            -
                    @downsync_data_subscriptions_interval = 60
         | 
| 68 | 
            -
                  end
         | 
| 69 | 
            -
              
         | 
| 48 | 
            +
                  @downsync_data_subscriptions_interval = ENV["WAFRIS_DOWNSYNC_DATA_SUBSCRIPTIONS_INTERVAL"]&.to_i || 60
         | 
| 49 | 
            +
             | 
| 70 50 | 
             
                  # Set Downsync URL - Optional
         | 
| 71 51 | 
             
                  # Used for both DataSubscription and CustomRules
         | 
| 72 | 
            -
                   | 
| 73 | 
            -
             | 
| 74 | 
            -
                  else
         | 
| 75 | 
            -
                    @downsync_url = 'https://distributor.wafris.org/v2/downsync'
         | 
| 76 | 
            -
                  end
         | 
| 77 | 
            -
                  
         | 
| 52 | 
            +
                  @downsync_url = ENV["WAFRIS_DOWNSYNC_URL"] || "https://distributor.wafris.org/v2/downsync"
         | 
| 53 | 
            +
             | 
| 78 54 | 
             
                  # UPSYNC - Optional
         | 
| 79 55 | 
             
                  # Set Upsync URL
         | 
| 80 | 
            -
                   | 
| 81 | 
            -
             | 
| 82 | 
            -
                  else
         | 
| 83 | 
            -
                    @upsync_url = 'https://collector.wafris.org/v2/upsync' 
         | 
| 84 | 
            -
                  end
         | 
| 85 | 
            -
              
         | 
| 56 | 
            +
                  @upsync_url = ENV["WAFRIS_UPSYNC_URL"] || "https://collector.wafris.org/v2/upsync"
         | 
| 57 | 
            +
             | 
| 86 58 | 
             
                  # Set Upsync Interval - Optional
         | 
| 87 | 
            -
                   | 
| 88 | 
            -
             | 
| 89 | 
            -
                  else
         | 
| 90 | 
            -
                    @upsync_interval = 10
         | 
| 91 | 
            -
                  end
         | 
| 92 | 
            -
                
         | 
| 59 | 
            +
                  @upsync_interval = ENV["WAFRIS_UPSYNC_INTERVAL"]&.to_i || 10
         | 
| 60 | 
            +
             | 
| 93 61 | 
             
                  # Set Upsync Queued Request Limit - Optional
         | 
| 94 | 
            -
                   | 
| 95 | 
            -
             | 
| 96 | 
            -
                  else
         | 
| 97 | 
            -
                    @upsync_queue_limit = 250
         | 
| 98 | 
            -
                  end
         | 
| 99 | 
            -
              
         | 
| 62 | 
            +
                  @upsync_queue_limit = ENV["WAFRIS_UPSYNC_QUEUE_LIMIT"]&.to_i || 250
         | 
| 63 | 
            +
             | 
| 100 64 | 
             
                  # Set Maximium Body Size for Requests - Optional (in Megabytes)
         | 
| 101 | 
            -
                  if ENV[ | 
| 102 | 
            -
             | 
| 103 | 
            -
             | 
| 104 | 
            -
             | 
| 105 | 
            -
             | 
| 65 | 
            +
                  @max_body_size_mb = if ENV["WAFRIS_MAX_BODY_SIZE_MB"] && ENV["WAFRIS_MAX_BODY_SIZE_MB"].to_i > 0
         | 
| 66 | 
            +
                                        ENV["WAFRIS_MAX_BODY_SIZE_MB"].to_i
         | 
| 67 | 
            +
                                      else
         | 
| 68 | 
            +
                                        10
         | 
| 69 | 
            +
                                      end
         | 
| 106 70 |  | 
| 107 71 | 
             
                  # Upsync Queue Defaults
         | 
| 108 72 | 
             
                  @upsync_queue = []
         | 
| 109 73 | 
             
                  @last_upsync_timestamp = Time.now.to_i
         | 
| 110 | 
            -
             | 
| 74 | 
            +
             | 
| 111 75 | 
             
                  # Memory structure for rate limiting
         | 
| 112 76 | 
             
                  @rate_limiters = {}
         | 
| 113 | 
            -
             | 
| 77 | 
            +
             | 
| 114 78 | 
             
                  # Disable Upsync if Downsync API Key is invalid
         | 
| 115 79 | 
             
                  # This prevents the client from sending upsync requests
         | 
| 116 80 | 
             
                  # if the API key is known bad
         | 
| 117 | 
            -
                  @upsync_status =  | 
| 118 | 
            -
             | 
| 119 | 
            -
                  return true
         | 
| 120 | 
            -
              
         | 
| 81 | 
            +
                  @upsync_status = "Disabled"
         | 
| 121 82 | 
             
                end
         | 
| 122 83 |  | 
| 123 84 | 
             
                def current_config
         | 
| 124 | 
            -
             | 
| 125 85 | 
             
                  output = {}
         | 
| 126 86 |  | 
| 127 87 | 
             
                  instance_variables.each do |var|
         | 
| 128 88 | 
             
                    output[var.to_s] = instance_variable_get(var)
         | 
| 129 89 | 
             
                  end
         | 
| 130 90 |  | 
| 131 | 
            -
                   | 
| 132 | 
            -
             | 
| 91 | 
            +
                  output
         | 
| 133 92 | 
             
                end
         | 
| 134 93 |  | 
| 135 94 | 
             
                def create_settings
         | 
| 136 95 | 
             
                  @version = Wafris::VERSION
         | 
| 137 96 | 
             
                end
         | 
| 138 | 
            -
             | 
| 139 97 | 
             
              end
         | 
| 140 98 | 
             
            end
         | 
| @@ -7,19 +7,19 @@ module Wafris | |
| 7 7 | 
             
                end
         | 
| 8 8 |  | 
| 9 9 | 
             
                def self.suppress_logs?
         | 
| 10 | 
            -
                  suppressed_environments.include?(current_environment) || | 
| 11 | 
            -
             | 
| 10 | 
            +
                  suppressed_environments.include?(current_environment) ||
         | 
| 11 | 
            +
                    (ENV["WAFRIS_LOG_LEVEL"] && ENV["WAFRIS_LOG_LEVEL"] == "silent")
         | 
| 12 12 | 
             
                end
         | 
| 13 13 |  | 
| 14 14 | 
             
                def self.suppressed_environments
         | 
| 15 | 
            -
                  [ | 
| 15 | 
            +
                  ["test"] + (ENV["CI"] ? ["CI"] : [])
         | 
| 16 16 | 
             
                end
         | 
| 17 17 |  | 
| 18 18 | 
             
                def self.current_environment
         | 
| 19 19 | 
             
                  if defined?(Rails)
         | 
| 20 20 | 
             
                    Rails.env
         | 
| 21 21 | 
             
                  else
         | 
| 22 | 
            -
                    ENV[ | 
| 22 | 
            +
                    ENV["RACK_ENV"] || "development"
         | 
| 23 23 | 
             
                  end
         | 
| 24 24 | 
             
                end
         | 
| 25 25 | 
             
              end
         | 
    
        data/lib/wafris/middleware.rb
    CHANGED
    
    | @@ -9,32 +9,20 @@ module Wafris | |
| 9 9 |  | 
| 10 10 | 
             
                def call(env)
         | 
| 11 11 | 
             
                  request = Rack::Request.new(env)
         | 
| 12 | 
            -
                  wafris_request = WafrisRequest.new(request, env)
         | 
| 13 12 |  | 
| 14 13 | 
             
                  treatment = Wafris.evaluate(
         | 
| 15 | 
            -
                     | 
| 16 | 
            -
                    wafris_request.user_agent,
         | 
| 17 | 
            -
                    wafris_request.path,
         | 
| 18 | 
            -
                    wafris_request.parameters,
         | 
| 19 | 
            -
                    wafris_request.host,
         | 
| 20 | 
            -
                    wafris_request.request_method,
         | 
| 21 | 
            -
                    wafris_request.headers,
         | 
| 22 | 
            -
                    wafris_request.body,
         | 
| 23 | 
            -
                    wafris_request.request_id,
         | 
| 24 | 
            -
                    wafris_request.request_timestamp
         | 
| 14 | 
            +
                    WafrisRequest.new(request, env)
         | 
| 25 15 | 
             
                  )
         | 
| 26 16 |  | 
| 27 17 | 
             
                  # These values match what the client tests expect (200, 404, 403, 500)
         | 
| 28 | 
            -
                  if treatment ==  | 
| 18 | 
            +
                  if treatment == "Allowed" || treatment == "Passed"
         | 
| 29 19 | 
             
                    @app.call(env)
         | 
| 30 | 
            -
                  elsif treatment ==  | 
| 31 | 
            -
                    [403, { | 
| 20 | 
            +
                  elsif treatment == "Blocked"
         | 
| 21 | 
            +
                    [403, {"content-type" => "text/plain"}, ["Blocked"]]
         | 
| 32 22 | 
             
                  else
         | 
| 33 | 
            -
                     | 
| 34 | 
            -
                    [500, { 'content-type' => 'text/plain' }, ['Error']]
         | 
| 23 | 
            +
                    [500, {"content-type" => "text/plain"}, ["Error"]]
         | 
| 35 24 | 
             
                  end
         | 
| 36 | 
            -
             | 
| 37 | 
            -
                rescue StandardError => e
         | 
| 25 | 
            +
                rescue => e
         | 
| 38 26 | 
             
                  LogSuppressor.puts_log "[Wafris] Detailed Error: #{e.class} - #{e.message}"
         | 
| 39 27 | 
             
                  LogSuppressor.puts_log "[Wafris] Backtrace: #{e.backtrace.join("\n")}"
         | 
| 40 28 | 
             
                  @app.call(env)
         | 
    
        data/lib/wafris/proxy_filter.rb
    CHANGED
    
    | @@ -3,7 +3,7 @@ | |
| 3 3 | 
             
            module Wafris
         | 
| 4 4 | 
             
              module ProxyFilter
         | 
| 5 5 | 
             
                def self.set_filter
         | 
| 6 | 
            -
                  user_defined_proxies = ENV[ | 
| 6 | 
            +
                  user_defined_proxies = ENV["TRUSTED_PROXY_RANGES"].split(",") if ENV["TRUSTED_PROXY_RANGES"]
         | 
| 7 7 |  | 
| 8 8 | 
             
                  valid_ipv4_octet = /\.(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])/
         | 
| 9 9 |  | 
| @@ -21,4 +21,4 @@ module Wafris | |
| 21 21 | 
             
                  Rack::Request.ip_filter = lambda { |ip| trusted_proxies.match?(ip) }
         | 
| 22 22 | 
             
                end
         | 
| 23 23 | 
             
              end
         | 
| 24 | 
            -
            end
         | 
| 24 | 
            +
            end
         | 
    
        data/lib/wafris/railtie.rb
    CHANGED
    
    
    
        data/lib/wafris/version.rb
    CHANGED
    
    
| @@ -2,7 +2,7 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            module Wafris
         | 
| 4 4 | 
             
              class WafrisRequest
         | 
| 5 | 
            -
                attr_reader :ip, :user_agent, :path, :parameters, :host, : | 
| 5 | 
            +
                attr_reader :ip, :user_agent, :path, :parameters, :host, :method,
         | 
| 6 6 | 
             
                            :headers, :body, :request_id, :request_timestamp
         | 
| 7 7 |  | 
| 8 8 | 
             
                def initialize(request, env)
         | 
| @@ -11,25 +11,43 @@ module Wafris | |
| 11 11 | 
             
                  @path = encode_to_utf8(request.path)
         | 
| 12 12 | 
             
                  @parameters = encode_to_utf8(Rack::Utils.build_query(request.params))
         | 
| 13 13 | 
             
                  @host = encode_to_utf8(request.host.to_s)
         | 
| 14 | 
            -
                  @ | 
| 14 | 
            +
                  @method = encode_to_utf8(request.request_method)
         | 
| 15 15 | 
             
                  @headers = extract_headers(env)
         | 
| 16 | 
            -
                  @ | 
| 17 | 
            -
                  @request_id = env.fetch('action_dispatch.request_id', SecureRandom.uuid.to_s)
         | 
| 16 | 
            +
                  @request_id = env.fetch("action_dispatch.request_id", SecureRandom.uuid.to_s)
         | 
| 18 17 | 
             
                  @request_timestamp = Time.now.utc.to_i
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  @body = encode_to_utf8(request.body&.read)
         | 
| 20 | 
            +
                  request.body&.rewind
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                def data(treatment:, category:, rule:)
         | 
| 24 | 
            +
                  {
         | 
| 25 | 
            +
                    ip: @ip,
         | 
| 26 | 
            +
                    user_agent: @user_agent,
         | 
| 27 | 
            +
                    path: @path,
         | 
| 28 | 
            +
                    parameter: @parameters,
         | 
| 29 | 
            +
                    host: @host,
         | 
| 30 | 
            +
                    method: @method,
         | 
| 31 | 
            +
                    request_id: @request_id,
         | 
| 32 | 
            +
                    timestamp: @request_timestamp,
         | 
| 33 | 
            +
                    treatment: treatment,
         | 
| 34 | 
            +
                    category: category,
         | 
| 35 | 
            +
                    rule: rule
         | 
| 36 | 
            +
                  }
         | 
| 19 37 | 
             
                end
         | 
| 20 38 |  | 
| 21 39 | 
             
                private
         | 
| 22 40 |  | 
| 23 41 | 
             
                def extract_headers(env)
         | 
| 24 42 | 
             
                  env.each_with_object({}) do |(k, v), h|
         | 
| 25 | 
            -
                    h[k] = encode_to_utf8(v) if k.start_with?( | 
| 43 | 
            +
                    h[k] = encode_to_utf8(v) if k.start_with?("HTTP_")
         | 
| 26 44 | 
             
                  end
         | 
| 27 45 | 
             
                end
         | 
| 28 46 |  | 
| 29 47 | 
             
                def encode_to_utf8(value)
         | 
| 30 | 
            -
                  return  | 
| 48 | 
            +
                  return "" if value.nil?
         | 
| 31 49 |  | 
| 32 | 
            -
                  value&.dup&.force_encoding( | 
| 50 | 
            +
                  value&.dup&.force_encoding("UTF-8")
         | 
| 33 51 | 
             
                end
         | 
| 34 52 | 
             
              end
         | 
| 35 53 | 
             
            end
         | 
    
        data/lib/wafris.rb
    CHANGED
    
    | @@ -1,45 +1,41 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require  | 
| 4 | 
            -
            require  | 
| 5 | 
            -
            require  | 
| 6 | 
            -
            require  | 
| 7 | 
            -
            require  | 
| 3 | 
            +
            require "rails"
         | 
| 4 | 
            +
            require "sqlite3"
         | 
| 5 | 
            +
            require "ipaddr"
         | 
| 6 | 
            +
            require "httparty"
         | 
| 7 | 
            +
            require "awesome_print"
         | 
| 8 8 |  | 
| 9 | 
            -
            require  | 
| 10 | 
            -
            require  | 
| 11 | 
            -
            require  | 
| 12 | 
            -
            require  | 
| 13 | 
            -
            require  | 
| 14 | 
            -
            require  | 
| 9 | 
            +
            require "wafris/configuration"
         | 
| 10 | 
            +
            require "wafris/middleware"
         | 
| 11 | 
            +
            require "wafris/log_suppressor"
         | 
| 12 | 
            +
            require "wafris/proxy_filter"
         | 
| 13 | 
            +
            require "wafris/ip_resolver"
         | 
| 14 | 
            +
            require "wafris/wafris_request"
         | 
| 15 15 |  | 
| 16 | 
            -
            require  | 
| 16 | 
            +
            require "wafris/railtie" if defined?(Rails::Railtie)
         | 
| 17 17 |  | 
| 18 18 | 
             
            module Wafris
         | 
| 19 19 | 
             
              class << self
         | 
| 20 20 | 
             
                attr_accessor :configuration
         | 
| 21 21 |  | 
| 22 22 | 
             
                def configure
         | 
| 23 | 
            -
                   | 
| 24 | 
            -
             | 
| 25 | 
            -
                    yield(configuration)
         | 
| 26 | 
            -
                    
         | 
| 27 | 
            -
                    LogSuppressor.puts_log("[Wafris] Configuration settings created.")
         | 
| 28 | 
            -
                    configuration.create_settings
         | 
| 29 | 
            -
             | 
| 30 | 
            -
                  rescue StandardError => e
         | 
| 31 | 
            -
                    puts "[Wafris] firewall disabled due to: #{e.message}. Cannot connect via Wafris.configure. Please check your configuration settings. More info can be found at: https://github.com/Wafris/wafris-rb"
         | 
| 32 | 
            -
                  end 
         | 
| 23 | 
            +
                  self.configuration ||= Wafris::Configuration.new
         | 
| 24 | 
            +
                  yield(configuration)
         | 
| 33 25 |  | 
| 26 | 
            +
                  LogSuppressor.puts_log("[Wafris] Configuration settings created.")
         | 
| 27 | 
            +
                  configuration.create_settings
         | 
| 28 | 
            +
                rescue => e
         | 
| 29 | 
            +
                  puts "[Wafris] firewall disabled due to: #{e.message}. Cannot connect via Wafris.configure. Please check your configuration settings. More info can be found at: https://github.com/Wafris/wafris-rb"
         | 
| 34 30 | 
             
                end
         | 
| 35 31 |  | 
| 36 32 | 
             
                def zero_pad(number, length)
         | 
| 37 33 | 
             
                  number.to_s.rjust(length, "0")
         | 
| 38 34 | 
             
                end
         | 
| 39 | 
            -
             | 
| 35 | 
            +
             | 
| 40 36 | 
             
                def ip_to_decimal_lexical_string(ip)
         | 
| 41 37 | 
             
                  num = 0
         | 
| 42 | 
            -
             | 
| 38 | 
            +
             | 
| 43 39 | 
             
                  if ip.include?(":")
         | 
| 44 40 | 
             
                    ip = IPAddr.new(ip).to_string
         | 
| 45 41 | 
             
                    hex = ip.delete(":")
         | 
| @@ -52,41 +48,39 @@ module Wafris | |
| 52 48 | 
             
                      num = num * 256 + chunk.to_i
         | 
| 53 49 | 
             
                    end
         | 
| 54 50 | 
             
                  end
         | 
| 55 | 
            -
             | 
| 51 | 
            +
             | 
| 56 52 | 
             
                  str = num.to_s
         | 
| 57 53 | 
             
                  zero_pad(str, 39)
         | 
| 58 54 | 
             
                end
         | 
| 59 | 
            -
             | 
| 55 | 
            +
             | 
| 60 56 | 
             
                def ip_in_cidr_range(ip_address, table_name, db_connection)
         | 
| 61 57 | 
             
                  lexical_address = ip_to_decimal_lexical_string(ip_address)
         | 
| 62 58 | 
             
                  higher_value = db_connection.get_first_value("SELECT * FROM #{table_name} WHERE member > ? ORDER BY member ASC", [lexical_address])
         | 
| 63 59 | 
             
                  lower_value = db_connection.get_first_value("SELECT * FROM #{table_name} WHERE member < ? ORDER BY member DESC", [lexical_address])
         | 
| 64 | 
            -
             | 
| 60 | 
            +
             | 
| 65 61 | 
             
                  if higher_value.nil? || lower_value.nil?
         | 
| 66 | 
            -
                     | 
| 67 | 
            -
                  else | 
| 62 | 
            +
                    nil
         | 
| 63 | 
            +
                  else
         | 
| 68 64 | 
             
                    higher_compare = higher_value.split("-").last
         | 
| 69 65 | 
             
                    lower_compare = lower_value.split("-").last
         | 
| 70 | 
            -
             | 
| 66 | 
            +
             | 
| 71 67 | 
             
                    if higher_compare == lower_compare
         | 
| 72 | 
            -
                       | 
| 73 | 
            -
                    else
         | 
| 74 | 
            -
                      return nil
         | 
| 68 | 
            +
                      lower_compare
         | 
| 75 69 | 
             
                    end
         | 
| 76 70 | 
             
                  end
         | 
| 77 71 | 
             
                end
         | 
| 78 | 
            -
             | 
| 72 | 
            +
             | 
| 79 73 | 
             
                def get_country_code(ip, db_connection)
         | 
| 80 | 
            -
                  country_code = ip_in_cidr_range(ip,  | 
| 81 | 
            -
             | 
| 74 | 
            +
                  country_code = ip_in_cidr_range(ip, "country_ip_ranges", db_connection)
         | 
| 75 | 
            +
             | 
| 82 76 | 
             
                  if country_code
         | 
| 83 | 
            -
                    country_code | 
| 84 | 
            -
             | 
| 85 | 
            -
                  else | 
| 86 | 
            -
                     | 
| 87 | 
            -
                  end | 
| 77 | 
            +
                    country_code.split("_").first.split("G").last
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                  else
         | 
| 80 | 
            +
                    "ZZ"
         | 
| 81 | 
            +
                  end
         | 
| 88 82 | 
             
                end
         | 
| 89 | 
            -
             | 
| 83 | 
            +
             | 
| 90 84 | 
             
                def substring_match(request_property, table_name, db_connection)
         | 
| 91 85 | 
             
                  result = db_connection.execute("SELECT entries FROM #{table_name}")
         | 
| 92 86 | 
             
                  result.flatten.each do |entry|
         | 
| @@ -94,30 +88,28 @@ module Wafris | |
| 94 88 | 
             
                      return entry
         | 
| 95 89 | 
             
                    end
         | 
| 96 90 | 
             
                  end
         | 
| 97 | 
            -
                   | 
| 91 | 
            +
                  false
         | 
| 98 92 | 
             
                end
         | 
| 99 | 
            -
             | 
| 93 | 
            +
             | 
| 100 94 | 
             
                def exact_match(request_property, table_name, db_connection)
         | 
| 101 95 | 
             
                  result = db_connection.execute("SELECT entries FROM #{table_name} WHERE entries = ?", [request_property])
         | 
| 102 | 
            -
                   | 
| 96 | 
            +
                  result.any?
         | 
| 103 97 | 
             
                end
         | 
| 104 | 
            -
             | 
| 98 | 
            +
             | 
| 105 99 | 
             
                def check_rate_limit(ip, path, method, db_connection)
         | 
| 106 | 
            -
              
         | 
| 107 100 | 
             
                  # Correctly format the SQL query with placeholders
         | 
| 108 101 | 
             
                  limiters = db_connection.execute("SELECT * FROM blocked_rate_limits WHERE path = ? AND method = ?", [path, method])
         | 
| 109 | 
            -
             | 
| 102 | 
            +
             | 
| 110 103 | 
             
                  # If no rate limiters are matched
         | 
| 111 | 
            -
                  if limiters.empty? | 
| 104 | 
            +
                  if limiters.empty?
         | 
| 112 105 | 
             
                    return false
         | 
| 113 | 
            -
                  end | 
| 114 | 
            -
             | 
| 106 | 
            +
                  end
         | 
| 107 | 
            +
             | 
| 115 108 | 
             
                  current_timestamp = Time.now.to_i
         | 
| 116 | 
            -
             | 
| 109 | 
            +
             | 
| 117 110 | 
             
                  # If any rate limiters are matched
         | 
| 118 111 | 
             
                  # This implementation will block the request on any of the rate limiters
         | 
| 119 112 | 
             
                  limiters.each do |limiter|
         | 
| 120 | 
            -
              
         | 
| 121 113 | 
             
                    # Limiter array mapping
         | 
| 122 114 | 
             
                    # 0: id
         | 
| 123 115 | 
             
                    # 1: path
         | 
| @@ -125,99 +117,89 @@ module Wafris | |
| 125 117 | 
             
                    # 3: interval
         | 
| 126 118 | 
             
                    # 4: max_count
         | 
| 127 119 | 
             
                    # 5: rule_id
         | 
| 128 | 
            -
             | 
| 120 | 
            +
             | 
| 129 121 | 
             
                    interval = limiter[3]
         | 
| 130 | 
            -
                    max_count = limiter[4] | 
| 122 | 
            +
                    max_count = limiter[4]
         | 
| 131 123 | 
             
                    rule_id = limiter[5]
         | 
| 132 | 
            -
             | 
| 124 | 
            +
             | 
| 133 125 | 
             
                    # Expire old timestamps
         | 
| 134 126 | 
             
                    @configuration.rate_limiters.each do |ip, timestamps|
         | 
| 135 127 | 
             
                      # Removes timestamps older than the interval
         | 
| 136 | 
            -
             | 
| 128 | 
            +
             | 
| 137 129 | 
             
                      @configuration.rate_limiters[ip] = timestamps.select { |timestamp| timestamp > current_timestamp - interval }
         | 
| 138 | 
            -
             | 
| 130 | 
            +
             | 
| 139 131 | 
             
                      # Remove the IP if there are no more timestamps for the IP
         | 
| 140 132 | 
             
                      @configuration.rate_limiters.delete(ip) if @configuration.rate_limiters[ip].empty?
         | 
| 141 133 | 
             
                    end
         | 
| 142 | 
            -
             | 
| 143 | 
            -
                    # Check if the IP+Method is rate limited | 
| 144 | 
            -
             | 
| 134 | 
            +
             | 
| 135 | 
            +
                    # Check if the IP+Method is rate limited
         | 
| 136 | 
            +
             | 
| 145 137 | 
             
                    if @configuration.rate_limiters[ip] && @configuration.rate_limiters[ip].length >= max_count
         | 
| 146 138 | 
             
                      # Request is rate limited
         | 
| 147 | 
            -
             | 
| 148 | 
            -
                      
         | 
| 139 | 
            +
             | 
| 149 140 | 
             
                      return rule_id
         | 
| 150 | 
            -
             | 
| 141 | 
            +
             | 
| 151 142 | 
             
                    else
         | 
| 152 143 | 
             
                      # Request is not rate limited, so add the current timestamp
         | 
| 153 144 | 
             
                      if @configuration.rate_limiters[ip]
         | 
| 154 | 
            -
                        @configuration.rate_limiters[ip] << current_timestamp | 
| 155 | 
            -
                      else | 
| 145 | 
            +
                        @configuration.rate_limiters[ip] << current_timestamp
         | 
| 146 | 
            +
                      else
         | 
| 156 147 | 
             
                        @configuration.rate_limiters[ip] = [current_timestamp]
         | 
| 157 148 | 
             
                      end
         | 
| 158 | 
            -
             | 
| 149 | 
            +
             | 
| 159 150 | 
             
                      return false
         | 
| 160 151 | 
             
                    end
         | 
| 161 | 
            -
              
         | 
| 162 152 | 
             
                  end
         | 
| 163 | 
            -
              
         | 
| 164 153 | 
             
                end
         | 
| 165 | 
            -
             | 
| 154 | 
            +
             | 
| 166 155 | 
             
                def send_upsync_requests(requests_array)
         | 
| 167 | 
            -
                   | 
| 168 | 
            -
                    headers = {'Content-Type' => 'application/json'}
         | 
| 156 | 
            +
                  headers = { "Content-Type" => "application/json" }
         | 
| 169 157 |  | 
| 170 | 
            -
             | 
| 171 | 
            -
                      framework = "Rails v#{Rails::VERSION::STRING}"
         | 
| 172 | 
            -
                    else
         | 
| 173 | 
            -
                      framework = "Rack v#{Rack::VERSION::STRING}"
         | 
| 174 | 
            -
                    end
         | 
| 158 | 
            +
                  framework = defined?(Rails) ? "Rails v#{Rails::VERSION::STRING}" : "Rack v#{Rack.release}"
         | 
| 175 159 |  | 
| 176 | 
            -
             | 
| 177 | 
            -
             | 
| 178 | 
            -
             | 
| 179 | 
            -
             | 
| 180 | 
            -
             | 
| 181 | 
            -
             | 
| 182 | 
            -
             | 
| 183 | 
            -
             | 
| 160 | 
            +
                  body = {
         | 
| 161 | 
            +
                    meta: {
         | 
| 162 | 
            +
                      version: Wafris::VERSION,
         | 
| 163 | 
            +
                      client: "wafris-rb",
         | 
| 164 | 
            +
                      framework: framework
         | 
| 165 | 
            +
                    },
         | 
| 166 | 
            +
                    batch: requests_array
         | 
| 167 | 
            +
                  }.to_json
         | 
| 184 168 |  | 
| 185 | 
            -
             | 
| 169 | 
            +
                  url_and_api_key = @configuration.upsync_url + "/" + @configuration.api_key
         | 
| 186 170 |  | 
| 187 | 
            -
             | 
| 188 | 
            -
             | 
| 189 | 
            -
             | 
| 190 | 
            -
             | 
| 171 | 
            +
                  response = HTTParty.post(
         | 
| 172 | 
            +
                    url_and_api_key,
         | 
| 173 | 
            +
                    body: body,
         | 
| 174 | 
            +
                    headers: headers,
         | 
| 175 | 
            +
                    timeout: 10
         | 
| 176 | 
            +
                  )
         | 
| 191 177 |  | 
| 192 | 
            -
             | 
| 193 | 
            -
             | 
| 194 | 
            -
             | 
| 195 | 
            -
             | 
| 196 | 
            -
                    end    
         | 
| 197 | 
            -
                  rescue HTTParty::Error => e
         | 
| 198 | 
            -
                    LogSuppressor.puts_log("Upsync Error. Failed to send upsync requests: #{e.message}")
         | 
| 178 | 
            +
                  if response.code == 200
         | 
| 179 | 
            +
                    @configuration.upsync_status = "Complete"
         | 
| 180 | 
            +
                  else
         | 
| 181 | 
            +
                    LogSuppressor.puts_log("Upsync Error. HTTP Response: #{response.code}")
         | 
| 199 182 | 
             
                  end
         | 
| 200 | 
            -
             | 
| 183 | 
            +
                rescue HTTParty::Error => e
         | 
| 184 | 
            +
                  LogSuppressor.puts_log("Upsync Error. Failed to send upsync requests: #{e.message}")
         | 
| 201 185 | 
             
                end
         | 
| 202 | 
            -
             | 
| 203 | 
            -
                # This method is used to queue upsync requests. It takes in several parameters including | 
| 204 | 
            -
                # ip, user_agent, path, parameters, host, method, treatment, category, and rule. 
         | 
| 186 | 
            +
             | 
| 187 | 
            +
                # This method is used to queue upsync requests. It takes in several parameters including:
         | 
| 205 188 | 
             
                #
         | 
| 206 | 
            -
                # The 'treatment' parameter represents the action taken on the request, which can be | 
| 189 | 
            +
                # The 'treatment' parameter represents the action taken on the request, which can be
         | 
| 207 190 | 
             
                # 'Allowed', 'Blocked', or 'Passed'.
         | 
| 208 191 | 
             
                #
         | 
| 209 | 
            -
                # The 'category' parameter represents the category of the rule that was matched, such as | 
| 192 | 
            +
                # The 'category' parameter represents the category of the rule that was matched, such as
         | 
| 210 193 | 
             
                # 'blocked_ip', 'allowed_cidr', etc.
         | 
| 211 194 | 
             
                #
         | 
| 212 | 
            -
                # The 'rule' parameter represents the specific rule that was matched within the category | 
| 195 | 
            +
                # The 'rule' parameter represents the specific rule that was matched within the category
         | 
| 213 196 | 
             
                # ex: '192.23.5.4', 'SemRush', etc.
         | 
| 214 | 
            -
                def queue_upsync_request( | 
| 215 | 
            -
                  if @configuration.upsync_status !=  | 
| 216 | 
            -
                    @configuration.upsync_status =  | 
| 197 | 
            +
                def queue_upsync_request(request, treatment, category, rule)
         | 
| 198 | 
            +
                  if @configuration.upsync_status != "Disabled" || @configuration.upsync_status != "Uploading"
         | 
| 199 | 
            +
                    @configuration.upsync_status = "Uploading"
         | 
| 217 200 |  | 
| 218 201 | 
             
                    # Add request to the queue
         | 
| 219 | 
            -
                     | 
| 220 | 
            -
                    @configuration.upsync_queue << request
         | 
| 202 | 
            +
                    @configuration.upsync_queue << request.data(treatment: treatment, category: category, rule: rule)
         | 
| 221 203 |  | 
| 222 204 | 
             
                    # If the queue is full, send the requests to the upsync server
         | 
| 223 205 | 
             
                    if @configuration.upsync_queue.length >= @configuration.upsync_queue_limit || (Time.now.to_i - @configuration.last_upsync_timestamp) >= @configuration.upsync_interval
         | 
| @@ -228,7 +210,7 @@ module Wafris | |
| 228 210 | 
             
                      send_upsync_requests(requests_array)
         | 
| 229 211 | 
             
                    end
         | 
| 230 212 |  | 
| 231 | 
            -
                    @configuration.upsync_status =  | 
| 213 | 
            +
                    @configuration.upsync_status = "Enabled"
         | 
| 232 214 | 
             
                    # Return the treatment - used to return 403 or 200
         | 
| 233 215 |  | 
| 234 216 | 
             
                    message = "Request #{treatment}"
         | 
| @@ -236,103 +218,97 @@ module Wafris | |
| 236 218 | 
             
                    message += " | Rule: #{rule}" unless rule.blank?
         | 
| 237 219 | 
             
                    LogSuppressor.puts_log(message)
         | 
| 238 220 |  | 
| 239 | 
            -
                     | 
| 221 | 
            +
                    treatment
         | 
| 240 222 | 
             
                  else
         | 
| 241 | 
            -
                    @configuration.upsync_status =  | 
| 242 | 
            -
                     | 
| 223 | 
            +
                    @configuration.upsync_status = "Enabled"
         | 
| 224 | 
            +
                    "Passed"
         | 
| 243 225 | 
             
                  end
         | 
| 244 | 
            -
              
         | 
| 245 226 | 
             
                end
         | 
| 246 | 
            -
             | 
| 227 | 
            +
             | 
| 247 228 | 
             
                # Pulls the latest rules from the server
         | 
| 248 229 | 
             
                def downsync_db(db_rule_category, current_filename = nil)
         | 
| 249 | 
            -
              
         | 
| 250 230 | 
             
                  lockfile_path = "#{@configuration.db_file_path}/#{db_rule_category}.lockfile"
         | 
| 251 | 
            -
             | 
| 231 | 
            +
             | 
| 252 232 | 
             
                  # Ensure the directory exists before attempting to open the lockfile
         | 
| 253 233 | 
             
                  FileUtils.mkdir_p(@configuration.db_file_path) unless Dir.exist?(@configuration.db_file_path)
         | 
| 254 234 |  | 
| 255 235 | 
             
                  # Attempt to create a lockfile with exclusive access; skip if it exists
         | 
| 256 236 | 
             
                  begin
         | 
| 257 | 
            -
                    lockfile = File.open(lockfile_path, File::RDWR|File::CREAT|File::EXCL)
         | 
| 237 | 
            +
                    lockfile = File.open(lockfile_path, File::RDWR | File::CREAT | File::EXCL)
         | 
| 258 238 | 
             
                  rescue Errno::EEXIST
         | 
| 259 239 | 
             
                    LogSuppressor.puts_log("[Wafris][Downsync] Lockfile already exists, skipping downsync.")
         | 
| 260 240 | 
             
                    return
         | 
| 261 241 | 
             
                  rescue Exception => e
         | 
| 262 242 | 
             
                    LogSuppressor.puts_log("[Wafris][Downsync] Error creating lockfile: #{e.message}")
         | 
| 263 243 | 
             
                  end
         | 
| 264 | 
            -
             | 
| 244 | 
            +
             | 
| 265 245 | 
             
                  begin
         | 
| 266 246 | 
             
                    # Actual Downsync operations
         | 
| 267 247 | 
             
                    filename = ""
         | 
| 268 | 
            -
             | 
| 269 | 
            -
                     | 
| 270 | 
            -
                      framework = "Rails v#{Rails::VERSION::STRING}"
         | 
| 271 | 
            -
                    else
         | 
| 272 | 
            -
                      framework = "Rack v#{Rack::VERSION::STRING}"
         | 
| 273 | 
            -
                    end
         | 
| 248 | 
            +
             | 
| 249 | 
            +
                    framework = defined?(Rails) ? "Rails v#{Rails::VERSION::STRING}" : "Rack v#{Rack.release}"
         | 
| 274 250 |  | 
| 275 251 | 
             
                    data = {
         | 
| 276 252 | 
             
                      client_db: current_filename,
         | 
| 277 253 | 
             
                      process_id: Process.pid,
         | 
| 278 254 | 
             
                      hostname: Socket.gethostname,
         | 
| 279 255 | 
             
                      version: Wafris::VERSION,
         | 
| 280 | 
            -
                      client:  | 
| 256 | 
            +
                      client: "wafris-rb",
         | 
| 281 257 | 
             
                      framework: framework
         | 
| 282 258 | 
             
                    }
         | 
| 283 259 |  | 
| 284 260 | 
             
                    # Check server for new rules including process id
         | 
| 285 | 
            -
                    #puts "Downloading from #{@configuration.downsync_url}/#{db_rule_category}/#{@configuration.api_key}?current_version=#{current_filename}&process_id=#{Process.pid}"
         | 
| 261 | 
            +
                    # puts "Downloading from #{@configuration.downsync_url}/#{db_rule_category}/#{@configuration.api_key}?current_version=#{current_filename}&process_id=#{Process.pid}"
         | 
| 286 262 | 
             
                    uri = "#{@configuration.downsync_url}/#{db_rule_category}/#{@configuration.api_key}?#{data.to_query}"
         | 
| 287 | 
            -
             | 
| 263 | 
            +
             | 
| 288 264 | 
             
                    response = HTTParty.get(
         | 
| 289 265 | 
             
                      uri,
         | 
| 290 266 | 
             
                      follow_redirects: true,   # Enable following redirects
         | 
| 291 267 | 
             
                      max_redirects: 2          # Maximum number of redirects to follow
         | 
| 292 268 | 
             
                    )
         | 
| 293 | 
            -
             | 
| 269 | 
            +
             | 
| 294 270 | 
             
                    # TODO: What to do if timeout
         | 
| 295 | 
            -
                    # TODO: What to do if error | 
| 271 | 
            +
                    # TODO: What to do if error
         | 
| 296 272 |  | 
| 297 273 | 
             
                    if response.code == 401
         | 
| 298 | 
            -
                      @configuration.upsync_status =  | 
| 274 | 
            +
                      @configuration.upsync_status = "Disabled"
         | 
| 299 275 | 
             
                      LogSuppressor.puts_log("[Wafris][Downsync] Unauthorized: Bad or missing API key")
         | 
| 300 276 | 
             
                      LogSuppressor.puts_log("[Wafris][Downsync] API Key: #{@configuration.api_key}")
         | 
| 301 277 | 
             
                      filename = current_filename
         | 
| 302 | 
            -
             | 
| 278 | 
            +
             | 
| 303 279 | 
             
                    elsif response.code == 304
         | 
| 304 | 
            -
                      @configuration.upsync_status =  | 
| 280 | 
            +
                      @configuration.upsync_status = "Enabled"
         | 
| 305 281 | 
             
                      LogSuppressor.puts_log("[Wafris][Downsync] No new rules to download")
         | 
| 306 | 
            -
             | 
| 282 | 
            +
             | 
| 307 283 | 
             
                      filename = current_filename
         | 
| 308 | 
            -
             | 
| 284 | 
            +
             | 
| 309 285 | 
             
                    elsif response.code == 200
         | 
| 310 | 
            -
                      @configuration.upsync_status =  | 
| 311 | 
            -
             | 
| 286 | 
            +
                      @configuration.upsync_status = "Enabled"
         | 
| 287 | 
            +
             | 
| 312 288 | 
             
                      if current_filename
         | 
| 313 289 | 
             
                        old_file_name = current_filename
         | 
| 314 290 | 
             
                      end
         | 
| 315 | 
            -
             | 
| 316 | 
            -
                      # Extract the filename from the response | 
| 317 | 
            -
                      content_disposition = response.headers[ | 
| 318 | 
            -
                      filename = content_disposition.split( | 
| 319 | 
            -
             | 
| 320 | 
            -
                      # Save the body of the response to a new SQLite file | 
| 321 | 
            -
                      File. | 
| 322 | 
            -
             | 
| 291 | 
            +
             | 
| 292 | 
            +
                      # Extract the filename from the response
         | 
| 293 | 
            +
                      content_disposition = response.headers["content-disposition"]
         | 
| 294 | 
            +
                      filename = content_disposition.split("filename=")[1].strip
         | 
| 295 | 
            +
             | 
| 296 | 
            +
                      # Save the body of the response to a new SQLite file
         | 
| 297 | 
            +
                      File.binwrite(@configuration.db_file_path + "/" + filename, response.body)
         | 
| 298 | 
            +
             | 
| 323 299 | 
             
                      # Write the filename into the db_category.modfile
         | 
| 324 | 
            -
                      File. | 
| 325 | 
            -
             | 
| 300 | 
            +
                      File.write("#{@configuration.db_file_path}/#{db_rule_category}.modfile", filename)
         | 
| 301 | 
            +
             | 
| 326 302 | 
             
                      # Sanity check that the downloaded db file has tables
         | 
| 327 303 | 
             
                      # not empty or corrupted
         | 
| 328 | 
            -
                      db = SQLite3::Database.new @configuration.db_file_path + "/" + filename | 
| 304 | 
            +
                      db = SQLite3::Database.new @configuration.db_file_path + "/" + filename
         | 
| 329 305 | 
             
                      if db.execute("SELECT name FROM sqlite_master WHERE type='table';").any?
         | 
| 330 | 
            -
             | 
| 331 | 
            -
             | 
| 332 | 
            -
             | 
| 333 | 
            -
                            File.delete(@configuration.db_file_path + "/" + old_file_name) | 
| 334 | 
            -
                            end
         | 
| 306 | 
            +
                        # Remove the old database file
         | 
| 307 | 
            +
                        if old_file_name
         | 
| 308 | 
            +
                          if File.exist?(@configuration.db_file_path + "/" + old_file_name)
         | 
| 309 | 
            +
                            File.delete(@configuration.db_file_path + "/" + old_file_name)
         | 
| 335 310 | 
             
                          end
         | 
| 311 | 
            +
                        end
         | 
| 336 312 |  | 
| 337 313 | 
             
                      # DB file is bad or empty so keep using whatever we have now
         | 
| 338 314 | 
             
                      else
         | 
| @@ -340,210 +316,163 @@ module Wafris | |
| 340 316 | 
             
                        LogSuppressor.puts_log("[Wafris][Downsync] DB Error - No tables exist in the db file #{@configuration.db_file_path}/#{filename}")
         | 
| 341 317 | 
             
                      end
         | 
| 342 318 |  | 
| 343 | 
            -
                      
         | 
| 344 319 | 
             
                    end
         | 
| 345 | 
            -
             | 
| 346 | 
            -
                  rescue Exception => e
         | 
| 320 | 
            +
                  rescue => e
         | 
| 347 321 | 
             
                    LogSuppressor.puts_log("[Wafris][Downsync] Error downloading rules: #{e.message}")
         | 
| 348 | 
            -
             | 
| 322 | 
            +
             | 
| 349 323 | 
             
                  # This gets set even if the API key is bad or other issues
         | 
| 350 324 | 
             
                  # to prevent hammering the distribution server on every request
         | 
| 351 325 | 
             
                  ensure
         | 
| 352 | 
            -
                
         | 
| 353 326 | 
             
                    # Reset the modified time of the modfile
         | 
| 354 327 | 
             
                    unless File.exist?("#{@configuration.db_file_path}/#{db_rule_category}.modfile")
         | 
| 355 | 
            -
                      File.new("#{@configuration.db_file_path}/#{db_rule_category}.modfile",  | 
| 328 | 
            +
                      File.new("#{@configuration.db_file_path}/#{db_rule_category}.modfile", "w")
         | 
| 356 329 | 
             
                    end
         | 
| 357 | 
            -
             | 
| 330 | 
            +
             | 
| 358 331 | 
             
                    # Set the modified time of the modfile to the current time
         | 
| 359 332 | 
             
                    File.utime(Time.now, Time.now, "#{@configuration.db_file_path}/#{db_rule_category}.modfile")
         | 
| 360 | 
            -
             | 
| 333 | 
            +
             | 
| 361 334 | 
             
                    # Ensure the lockfile is removed after operations
         | 
| 362 335 | 
             
                    lockfile.close
         | 
| 363 336 | 
             
                    File.delete(lockfile_path)
         | 
| 364 337 | 
             
                  end
         | 
| 365 | 
            -
             | 
| 366 | 
            -
                   | 
| 367 | 
            -
              
         | 
| 338 | 
            +
             | 
| 339 | 
            +
                  filename
         | 
| 368 340 | 
             
                end
         | 
| 369 | 
            -
             | 
| 370 | 
            -
                # Returns the current database file, | 
| 341 | 
            +
             | 
| 342 | 
            +
                # Returns the current database file,
         | 
| 371 343 | 
             
                # if the file is older than the interval, it will download the latest db
         | 
| 372 344 | 
             
                # if the file doesn't exist, it will download the latest db
         | 
| 373 345 | 
             
                # if the lockfile exists, it will return the current db
         | 
| 374 346 | 
             
                def current_db(db_rule_category)
         | 
| 375 | 
            -
             | 
| 376 | 
            -
             | 
| 377 | 
            -
             | 
| 378 | 
            -
             | 
| 379 | 
            -
             | 
| 380 | 
            -
             | 
| 381 | 
            -
              
         | 
| 347 | 
            +
                  interval = if db_rule_category == "custom_rules"
         | 
| 348 | 
            +
                               @configuration.downsync_custom_rules_interval
         | 
| 349 | 
            +
                             else
         | 
| 350 | 
            +
                               @configuration.downsync_data_subscriptions_interval
         | 
| 351 | 
            +
                             end
         | 
| 352 | 
            +
             | 
| 382 353 | 
             
                  # Checks for existing current modfile, which contains the current db filename
         | 
| 383 354 | 
             
                  if File.exist?("#{@configuration.db_file_path}/#{db_rule_category}.modfile")
         | 
| 384 | 
            -
             | 
| 355 | 
            +
             | 
| 385 356 | 
             
                    LogSuppressor.puts_log("[Wafris][Downsync] Modfile exists, skipping downsync")
         | 
| 386 357 |  | 
| 387 358 | 
             
                    # Get last Modified Time and current database file name
         | 
| 388 359 | 
             
                    last_db_synctime = File.mtime("#{@configuration.db_file_path}/#{db_rule_category}.modfile").to_i
         | 
| 389 360 | 
             
                    returned_db = File.read("#{@configuration.db_file_path}/#{db_rule_category}.modfile").strip
         | 
| 390 | 
            -
             | 
| 361 | 
            +
             | 
| 391 362 | 
             
                    LogSuppressor.puts_log("[Wafris][Downsync] Modfile Last Modified Time: #{last_db_synctime}")
         | 
| 392 | 
            -
                    LogSuppressor.puts_log("[Wafris][Downsync] DB in Modfile: #{returned_db}") | 
| 363 | 
            +
                    LogSuppressor.puts_log("[Wafris][Downsync] DB in Modfile: #{returned_db}")
         | 
| 393 364 |  | 
| 394 | 
            -
                    # Check if the db file is older than the interval | 
| 365 | 
            +
                    # Check if the db file is older than the interval
         | 
| 395 366 | 
             
                    if (Time.now.to_i - last_db_synctime) > interval
         | 
| 396 | 
            -
             | 
| 367 | 
            +
             | 
| 397 368 | 
             
                      LogSuppressor.puts_log("[Wafris][Downsync] DB is older than the interval")
         | 
| 398 369 |  | 
| 399 370 | 
             
                      # Make sure that another process isn't already downloading the rules
         | 
| 400 371 | 
             
                      if !File.exist?("#{@configuration.db_file_path}/#{db_rule_category}.lockfile")
         | 
| 401 | 
            -
                        returned_db = downsync_db(db_rule_category, returned_db) | 
| 372 | 
            +
                        returned_db = downsync_db(db_rule_category, returned_db)
         | 
| 402 373 | 
             
                      end
         | 
| 403 | 
            -
             | 
| 404 | 
            -
                       | 
| 405 | 
            -
             | 
| 374 | 
            +
             | 
| 375 | 
            +
                      returned_db
         | 
| 376 | 
            +
             | 
| 406 377 | 
             
                    # Current db is up to date
         | 
| 407 | 
            -
                    else | 
| 408 | 
            -
             | 
| 378 | 
            +
                    else
         | 
| 379 | 
            +
             | 
| 409 380 | 
             
                      LogSuppressor.puts_log("[Wafris][Downsync] DB is up to date")
         | 
| 410 381 |  | 
| 411 382 | 
             
                      returned_db = File.read("#{@configuration.db_file_path}/#{db_rule_category}.modfile").strip
         | 
| 412 | 
            -
             | 
| 383 | 
            +
             | 
| 413 384 | 
             
                      # If the modfile is empty (no db file name), return nil
         | 
| 414 385 | 
             
                      # this can happen if the the api key is bad
         | 
| 415 | 
            -
                      if returned_db ==  | 
| 416 | 
            -
                         | 
| 386 | 
            +
                      if returned_db == ""
         | 
| 387 | 
            +
                        ""
         | 
| 417 388 | 
             
                      else
         | 
| 418 | 
            -
                         | 
| 389 | 
            +
                        returned_db
         | 
| 419 390 | 
             
                      end
         | 
| 420 | 
            -
             | 
| 391 | 
            +
             | 
| 421 392 | 
             
                    end
         | 
| 422 | 
            -
             | 
| 393 | 
            +
             | 
| 423 394 | 
             
                  # No modfile exists, so download the latest db
         | 
| 424 | 
            -
                  else | 
| 425 | 
            -
             | 
| 395 | 
            +
                  else
         | 
| 396 | 
            +
             | 
| 426 397 | 
             
                    LogSuppressor.puts_log("[Wafris][Downsync] No modfile exists, downloading latest db")
         | 
| 427 398 |  | 
| 428 399 | 
             
                    # Make sure that another process isn't already downloading the rules
         | 
| 429 400 | 
             
                    if File.exist?("#{@configuration.db_file_path}/#{db_rule_category}.lockfile")
         | 
| 430 401 | 
             
                      LogSuppressor.puts_log("[Wafris][Downsync] Lockfile exists, skipping downsync")
         | 
| 431 402 | 
             
                      # Lockfile exists, but no modfile with a db filename
         | 
| 432 | 
            -
                       | 
| 403 | 
            +
                      nil
         | 
| 433 404 | 
             
                    else
         | 
| 434 | 
            -
             | 
| 405 | 
            +
             | 
| 435 406 | 
             
                      LogSuppressor.puts_log("[Wafris][Downsync] No modfile exists, downloading latest db")
         | 
| 436 407 | 
             
                      # No modfile exists, so download the latest db
         | 
| 437 408 | 
             
                      returned_db = downsync_db(db_rule_category, nil)
         | 
| 438 | 
            -
             | 
| 409 | 
            +
             | 
| 439 410 | 
             
                      if returned_db.nil?
         | 
| 440 | 
            -
                         | 
| 441 | 
            -
                      else | 
| 442 | 
            -
                         | 
| 411 | 
            +
                        nil
         | 
| 412 | 
            +
                      else
         | 
| 413 | 
            +
                        returned_db
         | 
| 443 414 | 
             
                      end
         | 
| 444 | 
            -
              
         | 
| 445 415 | 
             
                    end
         | 
| 446 | 
            -
             | 
| 447 | 
            -
                  end 
         | 
| 448 | 
            -
              
         | 
| 416 | 
            +
                  end
         | 
| 449 417 | 
             
                end
         | 
| 450 | 
            -
             | 
| 418 | 
            +
             | 
| 451 419 | 
             
                # This is the main loop that evaluates the request
         | 
| 452 420 | 
             
                # as well as sorts out when downsync and upsync should be called
         | 
| 453 | 
            -
                def evaluate( | 
| 421 | 
            +
                def evaluate(request)
         | 
| 454 422 | 
             
                  @configuration ||= Wafris::Configuration.new
         | 
| 455 423 |  | 
| 456 | 
            -
                  if @configuration.api_key.nil?
         | 
| 457 | 
            -
                    return "Passed"
         | 
| 458 | 
            -
                  else
         | 
| 459 | 
            -
                    rules_db_filename = current_db('custom_rules')
         | 
| 460 | 
            -
                    data_subscriptions_db_filename = current_db('data_subscriptions')
         | 
| 424 | 
            +
                  return "Passed" if @configuration.api_key.nil?
         | 
| 461 425 |  | 
| 462 | 
            -
             | 
| 426 | 
            +
                  rules_db_filename = current_db("custom_rules")
         | 
| 427 | 
            +
                  data_subscriptions_db_filename = current_db("data_subscriptions")
         | 
| 463 428 |  | 
| 464 | 
            -
             | 
| 465 | 
            -
             | 
| 466 | 
            -
                        SQLite3::Database.new "#{@configuration.db_file_path}/#{data_subscriptions_db_filename}"
         | 
| 429 | 
            +
                  # Checks to see if the filenames are present before loading the db
         | 
| 430 | 
            +
                  if rules_db_filename.to_s.strip != "" && data_subscriptions_db_filename.strip.to_s.strip != ""
         | 
| 467 431 |  | 
| 468 | 
            -
             | 
| 469 | 
            -
             | 
| 470 | 
            -
             | 
| 471 | 
            -
                                                    ip, request_id, request_timestamp)
         | 
| 472 | 
            -
                      end
         | 
| 432 | 
            +
                    rules_db = SQLite3::Database.new "#{@configuration.db_file_path}/#{rules_db_filename}"
         | 
| 433 | 
            +
                    data_subscriptions_db =
         | 
| 434 | 
            +
                      SQLite3::Database.new "#{@configuration.db_file_path}/#{data_subscriptions_db_filename}"
         | 
| 473 435 |  | 
| 474 | 
            -
             | 
| 475 | 
            -
             | 
| 476 | 
            -
             | 
| 477 | 
            -
             | 
| 436 | 
            +
                    ip = request.ip
         | 
| 437 | 
            +
                    return queue_upsync_request(request, "Allowed", "ai", ip) if exact_match(ip, "allowed_ips", rules_db)
         | 
| 438 | 
            +
                    return queue_upsync_request(request, "Allowed", "ac", ip) if ip_in_cidr_range(ip, "allowed_cidr_ranges", rules_db)
         | 
| 439 | 
            +
                    return queue_upsync_request(request, "Blocked", "bi", ip) if exact_match(ip, "blocked_ips", rules_db)
         | 
| 440 | 
            +
                    return queue_upsync_request(request, "Blocked", "bc", ip) if ip_in_cidr_range(ip, "blocked_cidr_ranges", rules_db)
         | 
| 478 441 |  | 
| 479 | 
            -
             | 
| 480 | 
            -
             | 
| 481 | 
            -
                        return queue_upsync_request(ip, user_agent, path, parameters, host, method, 'Blocked', 'bi', ip, request_id, request_timestamp)
         | 
| 482 | 
            -
                      end
         | 
| 442 | 
            +
                    country_code = get_country_code(ip, data_subscriptions_db)
         | 
| 443 | 
            +
                    return queue_upsync_request(request, "Blocked", "bs", "G_#{country_code}") if exact_match(country_code, "blocked_country_codes", rules_db)
         | 
| 483 444 |  | 
| 484 | 
            -
             | 
| 485 | 
            -
             | 
| 486 | 
            -
                        return queue_upsync_request(ip, user_agent, path, parameters, host, method, 'Blocked', 'bc', ip, request_id, request_timestamp)
         | 
| 487 | 
            -
                      end
         | 
| 445 | 
            +
                    # Blocked Reputation IP Ranges
         | 
| 446 | 
            +
                    return queue_upsync_request(request, "Blocked", "bs", "R") if ip_in_cidr_range(ip, "reputation_ip_ranges", data_subscriptions_db)
         | 
| 488 447 |  | 
| 489 | 
            -
             | 
| 490 | 
            -
             | 
| 491 | 
            -
                      if exact_match(country_code, 'blocked_country_codes', rules_db)
         | 
| 492 | 
            -
                        return queue_upsync_request(ip, user_agent, path, parameters, host, method, 'Blocked', 'bs', "G_#{country_code}", request_id, request_timestamp)
         | 
| 493 | 
            -
                      end 
         | 
| 448 | 
            +
                    user_agent_match = substring_match(request.user_agent, "blocked_user_agents", rules_db)
         | 
| 449 | 
            +
                    return queue_upsync_request(request, "Blocked", "bu", user_agent_match) if user_agent_match
         | 
| 494 450 |  | 
| 495 | 
            -
             | 
| 496 | 
            -
             | 
| 497 | 
            -
                        return queue_upsync_request(ip, user_agent, path, parameters, host, method, 'Blocked', 'bs', "R", request_id, request_timestamp)
         | 
| 498 | 
            -
                      end
         | 
| 451 | 
            +
                    path_match = substring_match(request.path, "blocked_paths", rules_db)
         | 
| 452 | 
            +
                    return queue_upsync_request(request, "Blocked", "bp", path_match) if path_match
         | 
| 499 453 |  | 
| 500 | 
            -
             | 
| 501 | 
            -
             | 
| 502 | 
            -
                      if user_agent_match
         | 
| 503 | 
            -
                        return queue_upsync_request(ip, user_agent, path, parameters, host, method, 'Blocked', 'bu', user_agent_match, request_id, request_timestamp)
         | 
| 504 | 
            -
                      end
         | 
| 454 | 
            +
                    parameters_match = substring_match(request.parameters, "blocked_parameters", rules_db)
         | 
| 455 | 
            +
                    return queue_upsync_request(request, "Blocked", "ba", parameters_match) if parameters_match
         | 
| 505 456 |  | 
| 506 | 
            -
             | 
| 507 | 
            -
             | 
| 508 | 
            -
             | 
| 509 | 
            -
             | 
| 510 | 
            -
             | 
| 511 | 
            -
             | 
| 512 | 
            -
             | 
| 513 | 
            -
                       | 
| 514 | 
            -
                      if parameters_match
         | 
| 515 | 
            -
                        return queue_upsync_request(ip, user_agent, path, parameters, host, method, 'Blocked', 'ba', parameters_match, request_id, request_timestamp)
         | 
| 516 | 
            -
                      end
         | 
| 517 | 
            -
              
         | 
| 518 | 
            -
                      # Blocked Hosts
         | 
| 519 | 
            -
                      if exact_match(host, 'blocked_hosts', rules_db)
         | 
| 520 | 
            -
                        return queue_upsync_request(ip, user_agent, path, parameters, host, method, 'Blocked', 'bh', host, request_id, request_timestamp)
         | 
| 521 | 
            -
                      end
         | 
| 522 | 
            -
              
         | 
| 523 | 
            -
                      # Blocked Methods
         | 
| 524 | 
            -
                      if exact_match(method, 'blocked_methods', rules_db)
         | 
| 525 | 
            -
                        return queue_upsync_request(ip, user_agent, path, parameters, host, method, 'Blocked', 'bm', method, request_id, request_timestamp)
         | 
| 526 | 
            -
                      end
         | 
| 527 | 
            -
              
         | 
| 528 | 
            -
                      # Rate Limiting
         | 
| 529 | 
            -
                      rule_id = check_rate_limit(ip, path, method, rules_db)
         | 
| 530 | 
            -
                      if rule_id
         | 
| 531 | 
            -
                        return queue_upsync_request(ip, user_agent, path, parameters, host, method, 'Blocked', 'brl', rule_id, request_id, request_timestamp)
         | 
| 532 | 
            -
                      end
         | 
| 533 | 
            -
              
         | 
| 457 | 
            +
                    return queue_upsync_request(request, "Blocked", "bh", request.host) if exact_match(request.host, "blocked_hosts", rules_db)
         | 
| 458 | 
            +
             | 
| 459 | 
            +
                    return queue_upsync_request(request, "Blocked", "bm", request.method) if exact_match(request.method, "blocked_methods", rules_db)
         | 
| 460 | 
            +
             | 
| 461 | 
            +
                    # Rate Limiting
         | 
| 462 | 
            +
                    rule_id = check_rate_limit(ip, request.path, request.method, rules_db)
         | 
| 463 | 
            +
                    if rule_id
         | 
| 464 | 
            +
                      return queue_upsync_request(request, "Blocked", "brl", rule_id)
         | 
| 534 465 | 
             
                    end
         | 
| 535 | 
            -
             | 
| 536 | 
            -
                    # Passed if no allow or block rules matched
         | 
| 537 | 
            -
                    return queue_upsync_request(ip, user_agent, path, parameters, host, method, 'Passed', 'passed', '-', request_id, request_timestamp)
         | 
| 466 | 
            +
                  end
         | 
| 538 467 |  | 
| 539 | 
            -
                   | 
| 540 | 
            -
             | 
| 541 | 
            -
             | 
| 542 | 
            -
                def debug(api_key)
         | 
| 468 | 
            +
                  # Passed if no allow or block rules matched
         | 
| 469 | 
            +
                  queue_upsync_request(request, "Passed", "passed", "-")
         | 
| 470 | 
            +
                end
         | 
| 543 471 |  | 
| 544 | 
            -
             | 
| 472 | 
            +
                def debug(api_key)
         | 
| 473 | 
            +
                  if ENV["WAFRIS_API_KEY"]
         | 
| 545 474 | 
             
                    puts "Wafris API Key environment variable is set."
         | 
| 546 | 
            -
                    puts " - API Key: #{ENV[ | 
| 475 | 
            +
                    puts " - API Key: #{ENV["WAFRIS_API_KEY"]}"
         | 
| 547 476 | 
             
                  else
         | 
| 548 477 | 
             
                    puts "Wafris API Key environment variable is not set."
         | 
| 549 478 | 
             
                  end
         | 
| @@ -573,7 +502,7 @@ module Wafris | |
| 573 502 | 
             
                  if File.exist?(settings.db_file_path + "/" + "custom_rules.modfile")
         | 
| 574 503 | 
             
                    puts "Custom Rules Modfile: #{settings.db_file_path}/custom_rules.modfile exists"
         | 
| 575 504 | 
             
                    puts " - Last Modified Time: #{File.mtime(settings.db_file_path + "/" + "custom_rules.modfile")}"
         | 
| 576 | 
            -
                    puts " - Contents: #{File. | 
| 505 | 
            +
                    puts " - Contents: #{File.read(settings.db_file_path + "/" + "custom_rules.modfile")}"
         | 
| 577 506 | 
             
                  else
         | 
| 578 507 | 
             
                    puts "Custom Rules Modfile: #{settings.db_file_path}/custom_rules.modfile does not exist."
         | 
| 579 508 | 
             
                  end
         | 
| @@ -590,15 +519,12 @@ module Wafris | |
| 590 519 | 
             
                  if File.exist?(settings.db_file_path + "/" + "data_subscriptions.modfile")
         | 
| 591 520 | 
             
                    puts "Data Subscriptions Modfile: #{settings.db_file_path}/data_subscriptions.modfile exists"
         | 
| 592 521 | 
             
                    puts " - Last Modified Time: #{File.mtime(settings.db_file_path + "/" + "data_subscriptions.modfile")}"
         | 
| 593 | 
            -
                    puts " - Contents: #{File. | 
| 522 | 
            +
                    puts " - Contents: #{File.read(settings.db_file_path + "/" + "data_subscriptions.modfile")}"
         | 
| 594 523 | 
             
                  else
         | 
| 595 524 | 
             
                    puts "Data Subscriptions Modfile: #{settings.db_file_path}/data_subscriptions.modfile does not exist."
         | 
| 596 525 | 
             
                  end
         | 
| 597 526 |  | 
| 598 | 
            -
             | 
| 599 | 
            -
             | 
| 600 | 
            -
                  return true
         | 
| 527 | 
            +
                  true
         | 
| 601 528 | 
             
                end
         | 
| 602 | 
            -
              
         | 
| 603 529 | 
             
              end
         | 
| 604 530 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: wafris
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 2.0. | 
| 4 | 
            +
              version: 2.0.5
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Michael Buckbee
         | 
| @@ -9,7 +9,7 @@ authors: | |
| 9 9 | 
             
            autorequire: 
         | 
| 10 10 | 
             
            bindir: bin
         | 
| 11 11 | 
             
            cert_chain: []
         | 
| 12 | 
            -
            date: 2024-10- | 
| 12 | 
            +
            date: 2024-10-23 00:00:00.000000000 Z
         | 
| 13 13 | 
             
            dependencies:
         | 
| 14 14 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 15 15 | 
             
              name: rack
         | 
| @@ -26,7 +26,7 @@ dependencies: | |
| 26 26 | 
             
                  - !ruby/object:Gem::Version
         | 
| 27 27 | 
             
                    version: '2.0'
         | 
| 28 28 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 29 | 
            -
              name:  | 
| 29 | 
            +
              name: awesome_print
         | 
| 30 30 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 31 31 | 
             
                requirements:
         | 
| 32 32 | 
             
                - - ">="
         | 
| @@ -40,21 +40,21 @@ dependencies: | |
| 40 40 | 
             
                  - !ruby/object:Gem::Version
         | 
| 41 41 | 
             
                    version: '0'
         | 
| 42 42 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 43 | 
            -
              name:  | 
| 43 | 
            +
              name: httparty
         | 
| 44 44 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 45 45 | 
             
                requirements:
         | 
| 46 46 | 
             
                - - ">="
         | 
| 47 47 | 
             
                  - !ruby/object:Gem::Version
         | 
| 48 | 
            -
                    version:  | 
| 48 | 
            +
                    version: 0.21.0
         | 
| 49 49 | 
             
              type: :runtime
         | 
| 50 50 | 
             
              prerelease: false
         | 
| 51 51 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 52 52 | 
             
                requirements:
         | 
| 53 53 | 
             
                - - ">="
         | 
| 54 54 | 
             
                  - !ruby/object:Gem::Version
         | 
| 55 | 
            -
                    version:  | 
| 55 | 
            +
                    version: 0.21.0
         | 
| 56 56 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 57 | 
            -
              name:  | 
| 57 | 
            +
              name: ipaddr
         | 
| 58 58 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 59 59 | 
             
                requirements:
         | 
| 60 60 | 
             
                - - ">="
         | 
| @@ -68,7 +68,7 @@ dependencies: | |
| 68 68 | 
             
                  - !ruby/object:Gem::Version
         | 
| 69 69 | 
             
                    version: '0'
         | 
| 70 70 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 71 | 
            -
              name:  | 
| 71 | 
            +
              name: sqlite3
         | 
| 72 72 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 73 73 | 
             
                requirements:
         | 
| 74 74 | 
             
                - - ">="
         | 
| @@ -81,6 +81,20 @@ dependencies: | |
| 81 81 | 
             
                - - ">="
         | 
| 82 82 | 
             
                  - !ruby/object:Gem::Version
         | 
| 83 83 | 
             
                    version: '0'
         | 
| 84 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 85 | 
            +
              name: debug
         | 
| 86 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 87 | 
            +
                requirements:
         | 
| 88 | 
            +
                - - ">="
         | 
| 89 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 90 | 
            +
                    version: 1.0.0
         | 
| 91 | 
            +
              type: :development
         | 
| 92 | 
            +
              prerelease: false
         | 
| 93 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 94 | 
            +
                requirements:
         | 
| 95 | 
            +
                - - ">="
         | 
| 96 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 97 | 
            +
                    version: 1.0.0
         | 
| 84 98 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 85 99 | 
             
              name: minitest
         | 
| 86 100 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -95,6 +109,20 @@ dependencies: | |
| 95 109 | 
             
                - - "~>"
         | 
| 96 110 | 
             
                  - !ruby/object:Gem::Version
         | 
| 97 111 | 
             
                    version: '5.1'
         | 
| 112 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 113 | 
            +
              name: mocha
         | 
| 114 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 115 | 
            +
                requirements:
         | 
| 116 | 
            +
                - - "~>"
         | 
| 117 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 118 | 
            +
                    version: 2.4.5
         | 
| 119 | 
            +
              type: :development
         | 
| 120 | 
            +
              prerelease: false
         | 
| 121 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 122 | 
            +
                requirements:
         | 
| 123 | 
            +
                - - "~>"
         | 
| 124 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 125 | 
            +
                    version: 2.4.5
         | 
| 98 126 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 99 127 | 
             
              name: pry
         | 
| 100 128 | 
             
              requirement: !ruby/object:Gem::Requirement
         |