lib/sqreen/actions.rb in sqreen-1.15.0-java vs lib/sqreen/actions.rb in sqreen-1.15.1
- old
+ new
@@ -1,9 +1,10 @@
# Copyright (c) 2018 Sqreen. All Rights Reserved.
# Please refer to our terms for more information: https://www.sqreen.io/terms.html
require 'ipaddr'
+require 'sqreen/trie'
require 'sqreen/log'
require 'sqreen/exception'
require 'sqreen/sdk'
require 'sqreen/frameworks'
require 'singleton'
@@ -22,25 +23,21 @@
# Where the currently loaded actions are stored. Singleton
class Repository
include Singleton
- def initialize
- @actions = {} # indexed by subclass
- @actions.default_proc = proc { |h, k| h[k] = [] }
+ def add(params, action)
+ action.class.index(params || {}, action)
end
- def <<(action)
- @actions[action.class] << action
+ def get(action_class, key)
+ action_class = Base.get_type_class(action_class) unless action_class.class == Class
+ action_class.actions_matching key
end
- def [](action_class)
- @actions[action_class]
- end
-
def clear
- @actions.clear
+ Base.known_subclasses.each(&:clear)
end
end
# @return [Sqreen::Actions::Base]
def self.deserialize_action(hash)
@@ -66,10 +63,12 @@
subclass.new(id, opts, hash['parameters'] || {})
end
# Base class for actions
+ # subclasses must also implement some methods in their singleton classes
+ # (actions_matching, index and clear)
class Base
attr_reader :id, :expiry, :send_response
def initialize(id, opts)
@id = id
@@ -122,14 +121,31 @@
def get_type_class(name)
@@subclasses[name]
end
+ def known_subclasses
+ @@subclasses.values
+ end
+
def known_types
@@subclasses.keys
end
+ # all actions matching, possibly already expired
+ def actions_matching(_key)
+ raise 'implement in singletons of subclasses'
+ end
+
+ def index(_params, _action)
+ raise 'implement in singletons of subclasses'
+ end
+
+ def clear
+ raise 'implement in singletons of subclasses'
+ end
+
def inherited(subclass)
class << subclass
public :new
end
end
@@ -141,76 +157,107 @@
@@subclasses[name] = self
end
end
end
- module IpRanges
- attr_reader :ranges
+ module IpRangesIndex
+ def add_prefix(prefix_str, data)
+ @trie_v4 ||= Sqreen::Trie.new
+ @trie_v6 ||= Sqreen::Trie.new(nil, nil, Socket::AF_INET6)
+ prefix = Sqreen::Prefix.from_str(prefix_str, data)
+ trie = prefix.family == Socket::AF_INET6 ? @trie_v6 : @trie_v4
+ trie.insert prefix
+ end
+ def matching_actions(client_ip)
+ parsed_ip = IPAddr.new(client_ip)
+ trie = parsed_ip.family == Socket::AF_INET6 ? @trie_v6 : @trie_v4
+ found = trie.search_matching(parsed_ip.to_i, parsed_ip.family)
+ return [] unless found.size > 0
+
+ Sqreen.log.debug("Client ip #{client_ip} matches #{found.inspect}")
+ found.map(&:data)
+ end
+
+ def clear
+ @trie_v4 = Sqreen::Trie.new
+ @trie_v6 = Sqreen::Trie.new(nil, nil, Socket::AF_INET6)
+ end
+ end
+
+ module IpRangeIndexedActionClass
+ include IpRangesIndex
+
+ def actions_matching(client_ip)
+ matching_actions client_ip
+ end
+
+ def index(params, action)
+ ranges = parse_ip_ranges params
+
+ ranges.each do |r|
+ add_prefix r, action
+ end
+ end
+
+ private
+
+ # returns array of prefixes in string form
def parse_ip_ranges(params)
ranges = params['ip_cidr']
unless ranges && ranges.is_a?(Array) && !ranges.empty?
raise 'no non-empty ip_cidr array present'
end
- @ranges = ranges.map &IPAddr.method(:new)
+ ranges
end
-
- def matches_ip?(client_ip)
- parsed_ip = IPAddr.new client_ip
- found = ranges.find { |r| r.include? parsed_ip }
- return false unless found
-
- Sqreen.log.debug("Client ip #{client_ip} matches #{found.inspect}")
- true
- end
end
# Block a list of IP address ranges. Standard "raise" behavior.
class BlockIp < Base
- include IpRanges
+ extend IpRangeIndexedActionClass
+
self.type_name = 'block_ip'
def initialize(id, opts, params = {})
+ # no need to store the ranges for this action, the index filter the class
super(id, opts)
- parse_ip_ranges params
end
def do_run(client_ip)
- return nil unless matches_ip? client_ip
e = Sqreen::AttackBlocked.new("Blocked client's IP #{client_ip} " \
- "(action: #{id} covering range(s) #{ranges}). No action is required")
- { :status => :raise, :exception => e }
+ "(action: #{id}). No action is required")
+ { :status => :raise, :exception => e, :skip_rem_cbs => true }
end
def event_properties(client_ip)
{ 'ip_address' => client_ip }
end
end
# Block a list of IP address ranges by forcefully redirecting the user
# to a specific URL.
class RedirectIp < Base
- include IpRanges
+ extend IpRangeIndexedActionClass
+
self.type_name = 'redirect_ip'
attr_reader :redirect_url
def initialize(id, opts, params = {})
super(id, opts)
@redirect_url = params['url']
raise "no url provided for action #{id}" unless @redirect_url
- parse_ip_ranges params
end
def do_run(client_ip)
- return nil unless matches_ip? client_ip
Sqreen.log.info "Will request redirect for client with IP #{client_ip} " \
- "(action: #{id} covering range(s) #{ranges})."
+ "(action: #{id})."
{
:status => :skip,
:new_return_value => [303, { 'Location' => @redirect_url }, ['']],
+ :skip_rem_cbs => true,
}
end
def event_properties(client_ip)
{ 'ip_address' => client_ip, 'url' => @redirect_url }
@@ -220,19 +267,49 @@
# Blocks a user at the point Sqreen::identify()
# or Sqreen::auth_track() are called
class BlockUser < Base
self.type_name = 'block_user'
+ class << self
+ def actions_matching(identity_params)
+ key = stringify_keys(identity_params)
+ actions = @idx[key]
+ actions || []
+ end
+
+ def index(params, action)
+ @idx ||= {}
+ users = params['users']
+ raise ::Sqreen::Exception, 'nil "users" param for block_user action' if users.nil?
+ raise ::Sqreen::Exception, '"users" param must be an array' unless users.is_a? Array
+
+ users.each do |u|
+ @idx[u] ||= []
+ @idx[u] << action
+ end
+ end
+
+ def clear
+ @idx = {}
+ end
+
+ private
+
+ def stringify_keys(hash)
+ Hash[
+ hash.map { |k, v| [k.to_s, v] }
+ ]
+ end
+ end
+
+ # BlockUser proper definition continues
+
def initialize(id, opts, params = {})
super(id, opts)
- @users = params['users']
- raise ::Sqreen::Exception, 'nil "users" param for block_user action' if @users.nil?
- raise ::Sqreen::Exception, '"users" param must be an array' unless @users.is_a? Array
end
def do_run(identity_params)
- return unless @users.include? stringify_keys(identity_params)
Sqreen.log.info(
"Will raise due to user being blocked by action #{id}. " \
"Blocked user identity: #{identity_params}"
)
@@ -247,17 +324,9 @@
}
end
def event_properties(identity_params)
{ 'user' => identity_params }
- end
-
- private
-
- def stringify_keys(hash)
- Hash[
- hash.map { |k, v| [k.to_s, v] }
- ]
end
end
end
end