2013-10-21 14:49:51 -04:00
|
|
|
require_dependency 'screening_model'
|
|
|
|
|
|
|
|
# A ScreenedIpAddress record represents an IP address or subnet that is being watched,
|
|
|
|
# and possibly blocked from creating accounts.
|
|
|
|
class ScreenedIpAddress < ActiveRecord::Base
|
|
|
|
|
|
|
|
include ScreeningModel
|
|
|
|
|
|
|
|
default_action :block
|
|
|
|
|
2013-10-22 16:30:30 -04:00
|
|
|
validates :ip_address, ip_address_format: true, presence: true
|
2013-10-21 14:49:51 -04:00
|
|
|
|
|
|
|
def self.watch(ip_address, opts={})
|
|
|
|
match_for_ip_address(ip_address) || create(opts.slice(:action_type).merge(ip_address: ip_address))
|
|
|
|
end
|
|
|
|
|
2013-11-05 11:24:13 -05:00
|
|
|
# In Rails 4.0.0, validators are run to handle invalid assignments to inet columns (as they should).
|
|
|
|
# In Rails 4.0.1, an exception is raised before validation happens, so we need this hack for
|
|
|
|
# inet/cidr columns:
|
2013-11-04 18:32:35 -05:00
|
|
|
def ip_address=(val)
|
2014-02-18 13:00:46 -05:00
|
|
|
if val.nil?
|
|
|
|
self.errors.add(:ip_address, :invalid)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2014-02-21 14:14:30 -05:00
|
|
|
return write_attribute(:ip_address, val) if val.is_a?(IPAddr)
|
|
|
|
|
2014-02-18 13:00:46 -05:00
|
|
|
num_wildcards = val.count('*')
|
|
|
|
if num_wildcards == 0
|
|
|
|
write_attribute(:ip_address, val)
|
|
|
|
else
|
2014-09-24 12:05:29 -04:00
|
|
|
v = val.gsub(/\/.*/, '') # strip ranges like "/16" from the end if present
|
2014-02-18 13:00:46 -05:00
|
|
|
if v[v.index('*')..-1] =~ /[^\.\*]/
|
|
|
|
self.errors.add(:ip_address, :invalid)
|
|
|
|
return
|
|
|
|
end
|
2014-09-24 12:05:29 -04:00
|
|
|
parts = v.split('.')
|
|
|
|
(4 - parts.size).times { parts << '*' } # support strings like 192.*
|
|
|
|
v = parts.join('.')
|
|
|
|
write_attribute(:ip_address, "#{v.gsub('*', '0')}/#{32 - (v.count('*') * 8)}")
|
2014-02-18 13:00:46 -05:00
|
|
|
end
|
2013-11-17 17:31:44 -05:00
|
|
|
|
|
|
|
# this gets even messier, Ruby 1.9.2 raised a different exception to Ruby 2.0.0
|
|
|
|
# handle both exceptions
|
|
|
|
rescue ArgumentError, IPAddr::InvalidAddressError
|
2013-11-04 18:32:35 -05:00
|
|
|
self.errors.add(:ip_address, :invalid)
|
|
|
|
end
|
|
|
|
|
2014-02-18 10:33:08 -05:00
|
|
|
# Return a string with the ip address and mask in standard format. e.g., "127.0.0.0/8".
|
|
|
|
# Ruby's IPAddr class has no method for getting this.
|
|
|
|
def ip_address_with_mask
|
|
|
|
if ip_address
|
|
|
|
mask = ip_address.instance_variable_get(:@mask_addr).to_s(2).count('1')
|
|
|
|
if mask == 32
|
|
|
|
ip_address.to_s
|
|
|
|
else
|
2014-08-14 14:20:52 -04:00
|
|
|
"#{ip_address}/#{ip_address.instance_variable_get(:@mask_addr).to_s(2).count('1')}"
|
2014-02-18 10:33:08 -05:00
|
|
|
end
|
|
|
|
else
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-10-21 14:49:51 -04:00
|
|
|
def self.match_for_ip_address(ip_address)
|
|
|
|
# The <<= operator on inet columns means "is contained within or equal to".
|
|
|
|
#
|
|
|
|
# Read more about PostgreSQL's inet data type here:
|
|
|
|
#
|
|
|
|
# http://www.postgresql.org/docs/9.1/static/datatype-net-types.html
|
|
|
|
# http://www.postgresql.org/docs/9.1/static/functions-net.html
|
2014-05-06 09:41:59 -04:00
|
|
|
find_by("'#{ip_address.to_s}' <<= ip_address")
|
2013-10-21 14:49:51 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.should_block?(ip_address)
|
2013-10-23 17:11:11 -04:00
|
|
|
exists_for_ip_address_and_action?(ip_address, actions[:block])
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.is_whitelisted?(ip_address)
|
|
|
|
exists_for_ip_address_and_action?(ip_address, actions[:do_nothing])
|
|
|
|
end
|
|
|
|
|
2014-09-04 18:50:27 -04:00
|
|
|
def self.exists_for_ip_address_and_action?(ip_address, action_type, opts={})
|
2013-10-21 14:49:51 -04:00
|
|
|
b = match_for_ip_address(ip_address)
|
2014-09-04 18:50:27 -04:00
|
|
|
found = (!!b and b.action_type == action_type)
|
|
|
|
b.record_match! if found and opts[:record_match] != false
|
|
|
|
found
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.block_login?(user, ip_address)
|
|
|
|
return false if user.nil?
|
|
|
|
return false if !user.admin?
|
|
|
|
return false if ScreenedIpAddress.where(action_type: actions[:allow_admin]).count == 0
|
|
|
|
return true if ip_address.nil?
|
|
|
|
!exists_for_ip_address_and_action?(ip_address, actions[:allow_admin], record_match: false)
|
2013-10-21 14:49:51 -04:00
|
|
|
end
|
|
|
|
end
|
2013-12-05 01:40:35 -05:00
|
|
|
|
|
|
|
# == Schema Information
|
|
|
|
#
|
|
|
|
# Table name: screened_ip_addresses
|
|
|
|
#
|
|
|
|
# id :integer not null, primary key
|
|
|
|
# ip_address :inet not null
|
|
|
|
# action_type :integer not null
|
|
|
|
# match_count :integer default(0), not null
|
|
|
|
# last_match_at :datetime
|
2014-08-27 01:19:25 -04:00
|
|
|
# created_at :datetime not null
|
|
|
|
# updated_at :datetime not null
|
2013-12-05 01:40:35 -05:00
|
|
|
#
|
|
|
|
# Indexes
|
|
|
|
#
|
|
|
|
# index_screened_ip_addresses_on_ip_address (ip_address) UNIQUE
|
|
|
|
# index_screened_ip_addresses_on_last_match_at (last_match_at)
|
|
|
|
#
|