From f15345289317780eaee6cbe35cab7f1d0080dd97 Mon Sep 17 00:00:00 2001 From: Ryan Breed Date: Tue, 4 Oct 2016 10:05:39 -0500 Subject: [PATCH 1/6] adding #include? unit tests adding private method implementations extracting Nexpose::IPRange and moving spec up --- lib/nexpose.rb | 1 + lib/nexpose/ip_range.rb | 150 +++++++++++++++++++ lib/nexpose/site.rb | 115 --------------- spec/nexpose/site/ip_range_spec.rb | 227 +++++++++++++++++++++++++++++ 4 files changed, 378 insertions(+), 115 deletions(-) create mode 100644 lib/nexpose/ip_range.rb diff --git a/lib/nexpose.rb b/lib/nexpose.rb index 3b3814de..17baaf5c 100644 --- a/lib/nexpose.rb +++ b/lib/nexpose.rb @@ -83,6 +83,7 @@ require 'nexpose/global_settings' require 'nexpose/group' require 'nexpose/dag' +require 'nexpose/ip_range' require 'nexpose/manage' require 'nexpose/multi_tenant_user' require 'nexpose/password_policy' diff --git a/lib/nexpose/ip_range.rb b/lib/nexpose/ip_range.rb new file mode 100644 index 00000000..18d6ac32 --- /dev/null +++ b/lib/nexpose/ip_range.rb @@ -0,0 +1,150 @@ +module Nexpose + # Object that represents a single IP address or an inclusive range of IP addresses. + # If to is nil then the from field will be used to specify a single IP Address only. + # + class IPRange + # Start of range *Required + attr_accessor :from + # End of range *Optional (If nil then IPRange is a single IP Address) + attr_accessor :to + + # @overload initialize(ip) + # @param [#to_s] from the IP single IP address. + # @example + # Nexpose::IPRange.new('192.168.1.0') + # + # @overload initialize(start_ip, end_ip) + # @param [#to_s] from the IP to start the range with. + # @param [#to_s] to the IP to end the range with. + # @example + # Nexpose::IPRange.new('192.168.1.0', '192.168.1.255') + # + # @overload initialize(cidr_range) + # @param [#to_s] from the CIDR notation IP address range. + # @example + # Nexpose::IPRange.new('192.168.1.0/24') + # @note The range will not be stripped of reserved IP addresses (such as + # x.x.x.0 and x.x.x.255). + # + # @return [IPRange] an IP address range of one or more addresses. + def initialize(from, to = nil) + @from = from + @to = to unless from == to + + return unless @to.nil? + + range = IPAddr.new(@from.to_s).to_range + unless range.one? + @from = range.first.to_s + @to = range.last.to_s + end + end + + # Size of the IP range. The total number of IP addresses represented + # by this range. + # + # @return [Fixnum] size of the range. + # + def size + return 1 if @to.nil? + from = IPAddr.new(@from) + to = IPAddr.new(@to) + (from..to).to_a.size + end + + include Comparable + + def <=>(other) + return 1 unless other.respond_to? :from + from = IPAddr.new(@from) + to = @to.nil? ? from : IPAddr.new(@to) + cf_from = IPAddr.new(other.from) + cf_to = IPAddr.new(other.to.nil? ? other.from : other.to) + if cf_to < from + 1 + elsif to < cf_from + -1 + else # Overlapping + 0 + end + end + + def ==(other) + eql?(other) + end + + def eql?(other) + return false unless other.respond_to? :from + @from == other.from && @to == other.to + end + + def include?(other) + case other + when IPAddr + include_ipaddr?(other) + when Nexpose::IPRange + include_iprange?(other) + when String + begin + other_addr = IPAddr.new(other) + rescue IPAddr::InvalidAddressError => invalid_address + warn "could not coerce \"#{other}\" to IPAddr at #{invalid_address.backtrace[0]}: #{invalid_address.cause.to_s}" + return false + rescue IPAddr::AddressFamilyError => address_family + warn "could not coerce \"#{other}\" to IPAddr at #{address_family.backtrace[0]}: #{address_family.cause.to_s}" + return false + end + include_ipaddr?(other_addr) + else + raise ArgumentError, "invalid type" + end + end + + def hash + to_xml.hash + end + + def as_xml + xml = REXML::Element.new('range') + xml.add_attributes({ 'from' => @from, 'to' => @to }) + xml + end + alias_method :to_xml_elem, :as_xml + + def to_xml + as_xml.to_s + end + + def to_s + return from.to_s if to.nil? + "#{from.to_s} - #{to.to_s}" + end + + def include_ipaddr?(other) + other_range = other.to_range + other_from = other_range.first + other_to = other_range.last + other_iprange = Nexpose::IPRange.new(other_from.to_s, other_to.to_s) + include_iprange?(other_iprange) + end + + def include_iprange?(other) + if (other.to==nil) && (self.to==nil) + eql?(other) + elsif (other.to!=nil) && (self.to==nil) + false + elsif (other.to==nil) && (self.to!=nil) + ip_from = IPAddr.new(self.from) + ip_to = IPAddr.new(self.to) + other_from = IPAddr.new(other.from) + (ip_from <= other_from) && (other_from <= ip_to) + else + ip_from = IPAddr.new(self.from) + ip_to = IPAddr.new(self.to) + other_from = IPAddr.new(other.from) + other_to = IPAddr.new(other.to) + (ip_from <= other_from) && (other_to <= ip_to) + end + end + end +end \ No newline at end of file diff --git a/lib/nexpose/site.rb b/lib/nexpose/site.rb index 8500718b..f34bf449 100644 --- a/lib/nexpose/site.rb +++ b/lib/nexpose/site.rb @@ -639,119 +639,4 @@ def to_s @host.to_s end end - - # Object that represents a single IP address or an inclusive range of IP addresses. - # If to is nil then the from field will be used to specify a single IP Address only. - # - class IPRange - # Start of range *Required - attr_accessor :from - # End of range *Optional (If nil then IPRange is a single IP Address) - attr_accessor :to - - # @overload initialize(ip) - # @param [#to_s] from the IP single IP address. - # @example - # Nexpose::IPRange.new('192.168.1.0') - # - # @overload initialize(start_ip, end_ip) - # @param [#to_s] from the IP to start the range with. - # @param [#to_s] to the IP to end the range with. - # @example - # Nexpose::IPRange.new('192.168.1.0', '192.168.1.255') - # - # @overload initialize(cidr_range) - # @param [#to_s] from the CIDR notation IP address range. - # @example - # Nexpose::IPRange.new('192.168.1.0/24') - # @note The range will not be stripped of reserved IP addresses (such as - # x.x.x.0 and x.x.x.255). - # - # @return [IPRange] an IP address range of one or more addresses. - def initialize(from, to = nil) - @from = from - @to = to unless from == to - - return unless @to.nil? - - range = IPAddr.new(@from.to_s).to_range - unless range.one? - @from = range.first.to_s - @to = range.last.to_s - end - end - - # Size of the IP range. The total number of IP addresses represented - # by this range. - # - # @return [Fixnum] size of the range. - # - def size - return 1 if @to.nil? - from = IPAddr.new(@from) - to = IPAddr.new(@to) - (from..to).to_a.size - end - - include Comparable - - def <=>(other) - return 1 unless other.respond_to? :from - from = IPAddr.new(@from) - to = @to.nil? ? from : IPAddr.new(@to) - cf_from = IPAddr.new(other.from) - cf_to = IPAddr.new(other.to.nil? ? other.from : other.to) - if cf_to < from - 1 - elsif to < cf_from - -1 - else # Overlapping - 0 - end - end - - def ==(other) - eql?(other) - end - - def eql?(other) - return false unless other.respond_to? :from - @from == other.from && @to == other.to - end - - def include?(single_ip) - return false unless single_ip.respond_to? :from - from = IPAddr.new(@from) - to = @to.nil? ? from : IPAddr.new(@to) - other = IPAddr.new(single_ip) - - if other < from - false - elsif to < other - false - else - true - end - end - - def hash - to_xml.hash - end - - def as_xml - xml = REXML::Element.new('range') - xml.add_attributes({ 'from' => @from, 'to' => @to }) - xml - end - alias_method :to_xml_elem, :as_xml - - def to_xml - as_xml.to_s - end - - def to_s - return from.to_s if to.nil? - "#{from.to_s} - #{to.to_s}" - end - end end diff --git a/spec/nexpose/site/ip_range_spec.rb b/spec/nexpose/site/ip_range_spec.rb index 7aadba7d..aeec2444 100644 --- a/spec/nexpose/site/ip_range_spec.rb +++ b/spec/nexpose/site/ip_range_spec.rb @@ -29,6 +29,233 @@ end end end + describe '#include?' do + context 'with IPAddr argument' do + let(:ip_range) { Nexpose::IPRange.new('192.168.1.64','192.168.1.127') } + it 'returns a false for valid IPv4 IPAddr outside of range' do + ipv4_outside = IPAddr.new('192.168.1.1') + expect(ip_range.include?(ipv4_outside)).to be_falsey + end + it 'returns a true for valid IPv4 IPAddr inside range' do + ipv4_inside = IPAddr.new('192.168.1.65') + expect(ip_range.include?(ipv4_inside)).to be_truthy + end + it 'returns a false for valid IPv4 network IPAddr outside of range' do + ipv4_network = IPAddr.new('192.168.1.0/29') + expect(ip_range.include?(ipv4_network)).to be_falsey + end + it 'returns a true for valid IPv4 IPAddr inside range' do + ipv4_network = IPAddr.new('192.168.1.64/29') + expect(ip_range.include?(ipv4_network)).to be_truthy + end + end + context 'with a Nexpose::IPRange argument' do + let(:ip_range) { Nexpose::IPRange.new('192.168.1.64','192.168.1.127') } + it 'returns a bool for IPRange of a single address' + it 'returns a bool for IPRange spanning multiple addresses' + end + context 'with a castable string argument' do + let(:ip_range) { Nexpose::IPRange.new('192.168.1.64','192.168.1.127') } + it 'returns bool for single IPv4 addresses' + it 'returns bool for masked IPv4 addresses' + it 'returns bool for single IPv6 addresses' + it 'returns bool for masked IPv6 addresses' + end + context 'with an uncastable argument' do + let(:range) { Nexpose::IPRange.new('192.168.1.64','192.168.1.127') } + it 'returns false for stringlike arguments that cannot be coerced' do + uncastable = 'kitten' + expect(range.include?(uncastable)).to be_falsey + end + it 'prints warning for stringlike arguments that raise IPAddr::InvalidAddressError' do + uncastable = 'kitten' + expect { range.include?(uncastable) }.to output(/could not coerce/).to_stderr + end + it 'returns false for stringlike arguments that raise IPAddr::AddressFamilyError' do + uncastable = '0' + expect { range.include?(uncastable) }.to output(/could not coerce/).to_stderr + end + it 'returns false for intlike arguments that cannot be coerced' do + unusable = 0 + expect { range.include?(unusable) }.to raise_error(ArgumentError,/invalid type/) + end + end + end + describe '#include_ipaddr?' do + context 'calling from a range of one ip' do + context 'with a /32 argument' do + let(:single) { Nexpose::IPRange.new('192.168.1.1') } + it 'returns false if the other ip is not equal' do + other = IPAddr.new('192.168.1.2') + expect(single.include_ipaddr?(other)).to be_falsey + end + it 'returns true if the other ip is equal' do + other = IPAddr.new('192.168.1.1') + expect(single.include_ipaddr?(other)).to be_truthy + end + end + context 'with a CIDR network argument ' do + let(:single_aligned) { Nexpose::IPRange.new('192.168.1.0') } + it 'returns false if self.from == other base and masklen != 32' do + cidr_28 = IPAddr.new('192.168.1.0/28') + expect(single_aligned.include_ipaddr?(cidr_28)).to be_falsey + end + it 'returns false if self.from != other base and masklen != 32' do + cidr_28_succ = IPAddr.new('192.168.1.1/28') + expect(single_aligned.include_ipaddr?(cidr_28_succ)).to be_falsey + end + + it 'returns true if self.from == other base and masklen == 32' do + cidr_32 = IPAddr.new('192.168.1.0/32') + expect(single_aligned.include_ipaddr?(cidr_32)).to be_truthy + end + it 'returns false if self.from != other base and masklen == 32' do + cidr_32_succ = IPAddr.new('192.168.1.1/32') + expect(single_aligned.include_ipaddr?(cidr_32_succ)).to be_falsey + end + end + context 'with an IPv6 address' do + let(:single) { Nexpose::IPRange.new('192.168.1.1') } + it 'returns false for an IPv6 address' do + ipv6 = IPAddr.new('2001:0:0:0:DB8:800:200C:417A') + expect(single.include_ipaddr?(ipv6)).to be_falsey + end + it 'returns false for an equivalent IPv6 address' do + ipv6_equiv = IPAddr.new('0:0:0:0:0:0:c0a8:0101') + expect(single.include_ipaddr?(ipv6_equiv)).to be_falsey + end + it 'returns false for an ipv4 compatible IPv6 address' do + ipv6_compat = IPAddr.new('192.168.1.1').ipv4_compat + expect(single.include_ipaddr?(ipv6_compat)).to be_falsey + end + end + end + context 'calling from a spanning range' do + + context 'with a single IPv4 argument' do + let(:span_10_100) { Nexpose::IPRange.new('192.168.1.10','192.168.1.100') } + it 'returns false if self.to < other' do + outside_right = IPAddr.new('192.168.1.101') + expect(span_10_100.include_ipaddr?(outside_right)).to be_falsey + end + it 'returns false if self.from > other' do + outside_left = IPAddr.new('192.168.1.1') + expect(span_10_100.include_ipaddr?(outside_left)).to be_falsey + end + it 'returns true if self.from==other' do + equal_from = IPAddr.new('192.168.1.10') + expect(span_10_100.include_ipaddr?(equal_from)).to be_truthy + end + it 'returns true if self.to==other' do + equal_to = IPAddr.new('192.168.1.100') + expect(span_10_100.include_ipaddr?(equal_to)).to be_truthy + end + it 'returns true if self.from < other < self.to' do + inside = IPAddr.new('192.168.1.50') + expect(span_10_100.include_ipaddr?(inside)).to be_truthy + end + end + context 'calling from an aligned range' do + it 'returns true if self == other' do + range = Nexpose::IPRange.new('192.168.1.64','192.168.1.127') + same_cidr = IPAddr.new('192.168.1.64/26') + expect(range.include_ipaddr?(same_cidr)).to be_truthy + end + + it 'returns false for uncovered left and uncovered right' do + range = Nexpose::IPRange.new('192.168.1.64','192.168.1.127') + uncovered_cidr = IPAddr.new('192.168.1.0/24') + expect(range.include_ipaddr?(uncovered_cidr)).to be_falsey + end + it 'returns false for uncovered left and equal right' do + range = Nexpose::IPRange.new('192.168.1.64','192.168.1.127') + uncovered_left_equal_right = IPAddr.new('192.168.1.64/25') + expect(range.include_ipaddr?(uncovered_left_equal_right)).to be_falsey + end + it 'returns false for equal left and uncovered right' do + range = Nexpose::IPRange.new('192.168.1.0','192.168.1.127') + equal_left_uncovered_right = IPAddr.new('192.168.1.0/24') + expect(range.include_ipaddr?(equal_left_uncovered_right)).to be_falsey + end + it 'returns false for included left and uncovered right' do + range = Nexpose::IPRange.new('192.168.1.64','192.168.1.95') + included_left_uncovered_right = IPAddr.new('192.168.1.65/26') + expect(range.include_ipaddr?(included_left_uncovered_right)).to be_falsey + end + it 'returns true for included left and equal right' do + range = Nexpose::IPRange.new('192.168.1.64','192.168.1.95') + included_left_equal_right = IPAddr.new('192.168.1.80/28') + expect(range.include_ipaddr?(included_left_equal_right)).to be_truthy + end + it 'returns true for equal left and included right' do + range = Nexpose::IPRange.new('192.168.1.64','192.168.1.95') + equal_left_included_right = IPAddr.new('192.168.1.64/28') + expect(range.include_ipaddr?(equal_left_included_right)).to be_truthy + end + it 'returns true for included left and included right' do + included_left_included_right = IPAddr.new('192.168.1.80/29') + range = Nexpose::IPRange.new('192.168.1.64','192.168.1.95') + expect(range.include_ipaddr?(included_left_included_right)).to be_truthy + end + end + end + end + describe '#include_iprange?' do + context 'calling from a single ip' do + let(:single) { Nexpose::IPRange.new('192.168.1.1') } + it 'returns true if self.from == other.from and other.to.nil?' do + other = Nexpose::IPRange.new('192.168.1.1') + expect(single.include_iprange?(other)).to be_truthy + end + it 'returns false for other.from < self.from and other.to.nil?'do + other = Nexpose::IPRange.new('192.168.1.0') + expect(single.include_iprange?(other)).to be_falsey + end + it 'returns false for other.from > self.from and other.to.nil?' do + other = Nexpose::IPRange.new('192.168.1.2') + expect(single.include_iprange?(other)).to be_falsey + end + it 'returns false for self.from == other.from and other.to not nil' do + other = Nexpose::IPRange.new('192.168.1.1','192.168.1.2') + expect(single.include_iprange?(other)).to be_falsey + end + it 'returns false for self.from < other.from and other.to not nil' do + other = Nexpose::IPRange.new('192.168.1.2','192.168.1.3') + expect(single.include_iprange?(other)).to be_falsey + end + it 'returns false for self.from > other.from and other.to not nil' do + other = Nexpose::IPRange.new('192.168.1.0','192.168.1.1') + expect(single.include_iprange?(other)).to be_falsey + end + end + context 'called from a spanning range' do + let(:spanning) { Nexpose::IPRange.new('192.168.1.1','192.168.1.100') } + it 'returns false for other.from < self.from' do + uncovered_left = Nexpose::IPRange.new('192.168.1.0','192.168.1.2') + expect(spanning.include_iprange?(uncovered_left)).to be_falsey + end + it 'returns false for self.to < other.to' do + uncovered_right = Nexpose::IPRange.new('192.168.1.2','192.168.1.105') + expect(spanning.include_iprange?(uncovered_right)).to be_falsey + end + it 'returns true for self.from < other.from && other.to < self.to' do + fully_included = Nexpose::IPRange.new('192.168.1.2','192.168.1.99') + expect(spanning.include_iprange?(fully_included)).to be_truthy + end + it 'returns true for self.from < other.from && other.to == self.to' do + same_right = Nexpose::IPRange.new('192.168.1.2','192.168.1.100') + expect(spanning.include_iprange?(same_right)).to be_truthy + end + it 'returns true for self.from == other.from && other.to < self.to' do + same_left = Nexpose::IPRange.new('192.168.1.1','192.168.1.3') + expect(spanning.include_iprange?(same_left)).to be_truthy + end + it 'returns true for self==other' do + same = Nexpose::IPRange.new('192.168.1.1','192.168.1.100') + expect(spanning.include_iprange?(same)).to be_truthy + end + end + end describe '#as_xml' do include Helpers::XML From 6ba19acd6b39805b48d498b3c41850df1bc9630f Mon Sep 17 00:00:00 2001 From: Ryan Breed Date: Wed, 5 Oct 2016 13:02:32 -0500 Subject: [PATCH 2/6] add documentation and annotations mark argument-specific include? implementations as private --- lib/nexpose/ip_range.rb | 25 ++++++- spec/nexpose/site/ip_range_spec.rb | 106 ++++++++++++++++++----------- 2 files changed, 89 insertions(+), 42 deletions(-) diff --git a/lib/nexpose/ip_range.rb b/lib/nexpose/ip_range.rb index 18d6ac32..d829aea9 100644 --- a/lib/nexpose/ip_range.rb +++ b/lib/nexpose/ip_range.rb @@ -78,6 +78,27 @@ def eql?(other) @from == other.from && @to == other.to end + # + # @overload include?(other) + # @param other [IPAddr] /32 IP address + # + # @overload include?(IPAddr) + # @param other [IPAddr] CIDR range + # + # @overload include?(other) + # @param other [IPRange] single IP address + # + # @overload include?(other) + # @param other [IPRange] IPRange spanning multiple IPs + # + # @overload include?(other) + # @param other [String] /32 IP address + # + # @overload include?(other) + # @param other [String] CIDR range + # + # @return [FalseClass]|[TrueClass] if other is bounded inclusively by #from and #to + # def include?(other) case other when IPAddr @@ -96,7 +117,7 @@ def include?(other) end include_ipaddr?(other_addr) else - raise ArgumentError, "invalid type" + raise ArgumentError, "invalid type: #{other.class.to_s} not one of IPAddr, String, Nexpose::IPRange" end end @@ -119,7 +140,7 @@ def to_s return from.to_s if to.nil? "#{from.to_s} - #{to.to_s}" end - + private def include_ipaddr?(other) other_range = other.to_range other_from = other_range.first diff --git a/spec/nexpose/site/ip_range_spec.rb b/spec/nexpose/site/ip_range_spec.rb index aeec2444..85aabcd5 100644 --- a/spec/nexpose/site/ip_range_spec.rb +++ b/spec/nexpose/site/ip_range_spec.rb @@ -51,15 +51,41 @@ end context 'with a Nexpose::IPRange argument' do let(:ip_range) { Nexpose::IPRange.new('192.168.1.64','192.168.1.127') } - it 'returns a bool for IPRange of a single address' - it 'returns a bool for IPRange spanning multiple addresses' + it 'returns true for single address IPRange bounded by #from and #to' do + other_single_iprange = Nexpose::IPRange.new('192.168.1.65') + expect(ip_range.include?(other_single_iprange)).to be_truthy + end + it 'returns false for single address IPRange not bounded by #from and #to' do + other_single_iprange = Nexpose::IPRange.new('192.168.1.1') + expect(ip_range.include?(other_single_iprange)).to be_falsey + end + it 'returns a true for multiple address IPRange bounded by #from and #to' do + other_multiple_iprange = Nexpose::IPRange.new('192.168.1.65', '192.168.1.100') + expect(ip_range.include?(other_multiple_iprange)).to be_truthy + end + it 'returns a false for multiple address IPRange not bounded by #from and #to' do + other_multiple_iprange = Nexpose::IPRange.new('192.168.1.1', '192.168.1.100') + expect(ip_range.include?(other_multiple_iprange)).to be_falsey + end end context 'with a castable string argument' do let(:ip_range) { Nexpose::IPRange.new('192.168.1.64','192.168.1.127') } - it 'returns bool for single IPv4 addresses' - it 'returns bool for masked IPv4 addresses' - it 'returns bool for single IPv6 addresses' - it 'returns bool for masked IPv6 addresses' + it 'returns true for single IPv4 addresses bounded by #from and #to' do + other_host_string = '192.168.1.90' + expect(ip_range.include?(other_host_string)).to be_truthy + end + it 'returns false for single IPv4 addresses not bounded by #from and #to' do + other_host_string = '192.168.1.200' + expect(ip_range.include?(other_host_string)).to be_falsey + end + it 'returns true for masked IPv4 addresses bounded by #from and #to' do + other_cidr_string = '192.168.1.96/29' + expect(ip_range.include?(other_cidr_string)).to be_truthy + end + it 'returns false for masked IPv4 addresses not bounded by #from and #to' do + other_cidr_string = '192.168.1.0/24' + expect(ip_range.include?(other_cidr_string)).to be_falsey + end end context 'with an uncastable argument' do let(:range) { Nexpose::IPRange.new('192.168.1.64','192.168.1.127') } @@ -87,46 +113,46 @@ let(:single) { Nexpose::IPRange.new('192.168.1.1') } it 'returns false if the other ip is not equal' do other = IPAddr.new('192.168.1.2') - expect(single.include_ipaddr?(other)).to be_falsey + expect(single.send(:include_ipaddr?,other)).to be_falsey end it 'returns true if the other ip is equal' do other = IPAddr.new('192.168.1.1') - expect(single.include_ipaddr?(other)).to be_truthy + expect(single.send(:include_ipaddr?,other)).to be_truthy end end context 'with a CIDR network argument ' do let(:single_aligned) { Nexpose::IPRange.new('192.168.1.0') } it 'returns false if self.from == other base and masklen != 32' do cidr_28 = IPAddr.new('192.168.1.0/28') - expect(single_aligned.include_ipaddr?(cidr_28)).to be_falsey + expect(single_aligned.send(:include_ipaddr?,cidr_28)).to be_falsey end it 'returns false if self.from != other base and masklen != 32' do cidr_28_succ = IPAddr.new('192.168.1.1/28') - expect(single_aligned.include_ipaddr?(cidr_28_succ)).to be_falsey + expect(single_aligned.send(:include_ipaddr?,cidr_28_succ)).to be_falsey end it 'returns true if self.from == other base and masklen == 32' do cidr_32 = IPAddr.new('192.168.1.0/32') - expect(single_aligned.include_ipaddr?(cidr_32)).to be_truthy + expect(single_aligned.send(:include_ipaddr?,cidr_32)).to be_truthy end it 'returns false if self.from != other base and masklen == 32' do cidr_32_succ = IPAddr.new('192.168.1.1/32') - expect(single_aligned.include_ipaddr?(cidr_32_succ)).to be_falsey + expect(single_aligned.send(:include_ipaddr?,cidr_32_succ)).to be_falsey end end context 'with an IPv6 address' do let(:single) { Nexpose::IPRange.new('192.168.1.1') } it 'returns false for an IPv6 address' do ipv6 = IPAddr.new('2001:0:0:0:DB8:800:200C:417A') - expect(single.include_ipaddr?(ipv6)).to be_falsey + expect(single.send(:include_ipaddr?,ipv6)).to be_falsey end it 'returns false for an equivalent IPv6 address' do ipv6_equiv = IPAddr.new('0:0:0:0:0:0:c0a8:0101') - expect(single.include_ipaddr?(ipv6_equiv)).to be_falsey + expect(single.send(:include_ipaddr?,ipv6_equiv)).to be_falsey end it 'returns false for an ipv4 compatible IPv6 address' do ipv6_compat = IPAddr.new('192.168.1.1').ipv4_compat - expect(single.include_ipaddr?(ipv6_compat)).to be_falsey + expect(single.send(:include_ipaddr?,ipv6_compat)).to be_falsey end end end @@ -136,66 +162,66 @@ let(:span_10_100) { Nexpose::IPRange.new('192.168.1.10','192.168.1.100') } it 'returns false if self.to < other' do outside_right = IPAddr.new('192.168.1.101') - expect(span_10_100.include_ipaddr?(outside_right)).to be_falsey + expect(span_10_100.send(:include_ipaddr?,outside_right)).to be_falsey end it 'returns false if self.from > other' do outside_left = IPAddr.new('192.168.1.1') - expect(span_10_100.include_ipaddr?(outside_left)).to be_falsey + expect(span_10_100.send(:include_ipaddr?,outside_left)).to be_falsey end it 'returns true if self.from==other' do equal_from = IPAddr.new('192.168.1.10') - expect(span_10_100.include_ipaddr?(equal_from)).to be_truthy + expect(span_10_100.send(:include_ipaddr?,equal_from)).to be_truthy end it 'returns true if self.to==other' do equal_to = IPAddr.new('192.168.1.100') - expect(span_10_100.include_ipaddr?(equal_to)).to be_truthy + expect(span_10_100.send(:include_ipaddr?,equal_to)).to be_truthy end it 'returns true if self.from < other < self.to' do inside = IPAddr.new('192.168.1.50') - expect(span_10_100.include_ipaddr?(inside)).to be_truthy + expect(span_10_100.send(:include_ipaddr?,inside)).to be_truthy end end context 'calling from an aligned range' do it 'returns true if self == other' do range = Nexpose::IPRange.new('192.168.1.64','192.168.1.127') same_cidr = IPAddr.new('192.168.1.64/26') - expect(range.include_ipaddr?(same_cidr)).to be_truthy + expect(range.send(:include_ipaddr?,same_cidr)).to be_truthy end it 'returns false for uncovered left and uncovered right' do range = Nexpose::IPRange.new('192.168.1.64','192.168.1.127') uncovered_cidr = IPAddr.new('192.168.1.0/24') - expect(range.include_ipaddr?(uncovered_cidr)).to be_falsey + expect(range.send(:include_ipaddr?,uncovered_cidr)).to be_falsey end it 'returns false for uncovered left and equal right' do range = Nexpose::IPRange.new('192.168.1.64','192.168.1.127') uncovered_left_equal_right = IPAddr.new('192.168.1.64/25') - expect(range.include_ipaddr?(uncovered_left_equal_right)).to be_falsey + expect(range.send(:include_ipaddr?,uncovered_left_equal_right)).to be_falsey end it 'returns false for equal left and uncovered right' do range = Nexpose::IPRange.new('192.168.1.0','192.168.1.127') equal_left_uncovered_right = IPAddr.new('192.168.1.0/24') - expect(range.include_ipaddr?(equal_left_uncovered_right)).to be_falsey + expect(range.send(:include_ipaddr?,equal_left_uncovered_right)).to be_falsey end it 'returns false for included left and uncovered right' do range = Nexpose::IPRange.new('192.168.1.64','192.168.1.95') included_left_uncovered_right = IPAddr.new('192.168.1.65/26') - expect(range.include_ipaddr?(included_left_uncovered_right)).to be_falsey + expect(range.send(:include_ipaddr?, included_left_uncovered_right)).to be_falsey end it 'returns true for included left and equal right' do range = Nexpose::IPRange.new('192.168.1.64','192.168.1.95') included_left_equal_right = IPAddr.new('192.168.1.80/28') - expect(range.include_ipaddr?(included_left_equal_right)).to be_truthy + expect(range.send(:include_ipaddr?, included_left_equal_right)).to be_truthy end it 'returns true for equal left and included right' do range = Nexpose::IPRange.new('192.168.1.64','192.168.1.95') equal_left_included_right = IPAddr.new('192.168.1.64/28') - expect(range.include_ipaddr?(equal_left_included_right)).to be_truthy + expect(range.send(:include_ipaddr?, equal_left_included_right)).to be_truthy end it 'returns true for included left and included right' do included_left_included_right = IPAddr.new('192.168.1.80/29') range = Nexpose::IPRange.new('192.168.1.64','192.168.1.95') - expect(range.include_ipaddr?(included_left_included_right)).to be_truthy + expect(range.send(:include_ipaddr?, included_left_included_right)).to be_truthy end end end @@ -205,54 +231,54 @@ let(:single) { Nexpose::IPRange.new('192.168.1.1') } it 'returns true if self.from == other.from and other.to.nil?' do other = Nexpose::IPRange.new('192.168.1.1') - expect(single.include_iprange?(other)).to be_truthy + expect(single.send(:include_iprange?, other)).to be_truthy end it 'returns false for other.from < self.from and other.to.nil?'do other = Nexpose::IPRange.new('192.168.1.0') - expect(single.include_iprange?(other)).to be_falsey + expect(single.send(:include_iprange?, other)).to be_falsey end it 'returns false for other.from > self.from and other.to.nil?' do other = Nexpose::IPRange.new('192.168.1.2') - expect(single.include_iprange?(other)).to be_falsey + expect(single.send(:include_iprange?, other)).to be_falsey end it 'returns false for self.from == other.from and other.to not nil' do other = Nexpose::IPRange.new('192.168.1.1','192.168.1.2') - expect(single.include_iprange?(other)).to be_falsey + expect(single.send(:include_iprange?, other)).to be_falsey end it 'returns false for self.from < other.from and other.to not nil' do other = Nexpose::IPRange.new('192.168.1.2','192.168.1.3') - expect(single.include_iprange?(other)).to be_falsey + expect(single.send(:include_iprange?, other)).to be_falsey end it 'returns false for self.from > other.from and other.to not nil' do other = Nexpose::IPRange.new('192.168.1.0','192.168.1.1') - expect(single.include_iprange?(other)).to be_falsey + expect(single.send(:include_iprange?, other)).to be_falsey end end context 'called from a spanning range' do let(:spanning) { Nexpose::IPRange.new('192.168.1.1','192.168.1.100') } it 'returns false for other.from < self.from' do uncovered_left = Nexpose::IPRange.new('192.168.1.0','192.168.1.2') - expect(spanning.include_iprange?(uncovered_left)).to be_falsey + expect(spanning.send(:include_iprange?, uncovered_left)).to be_falsey end it 'returns false for self.to < other.to' do uncovered_right = Nexpose::IPRange.new('192.168.1.2','192.168.1.105') - expect(spanning.include_iprange?(uncovered_right)).to be_falsey + expect(spanning.send(:include_iprange?, uncovered_right)).to be_falsey end it 'returns true for self.from < other.from && other.to < self.to' do fully_included = Nexpose::IPRange.new('192.168.1.2','192.168.1.99') - expect(spanning.include_iprange?(fully_included)).to be_truthy + expect(spanning.send(:include_iprange?, fully_included)).to be_truthy end it 'returns true for self.from < other.from && other.to == self.to' do same_right = Nexpose::IPRange.new('192.168.1.2','192.168.1.100') - expect(spanning.include_iprange?(same_right)).to be_truthy + expect(spanning.send(:include_iprange?, same_right)).to be_truthy end it 'returns true for self.from == other.from && other.to < self.to' do same_left = Nexpose::IPRange.new('192.168.1.1','192.168.1.3') - expect(spanning.include_iprange?(same_left)).to be_truthy + expect(spanning.send(:include_iprange?, same_left)).to be_truthy end it 'returns true for self==other' do same = Nexpose::IPRange.new('192.168.1.1','192.168.1.100') - expect(spanning.include_iprange?(same)).to be_truthy + expect(spanning.send(:include_iprange?, same)).to be_truthy end end end From d615dc1539b0b6343f9c46a233004077e3e73ac6 Mon Sep 17 00:00:00 2001 From: Ryan Breed Date: Sat, 8 Oct 2016 09:47:26 -0500 Subject: [PATCH 3/6] moving spec up a level --- lib/nexpose/ip_range.rb | 2 +- spec/nexpose/{site => }/ip_range_spec.rb | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename spec/nexpose/{site => }/ip_range_spec.rb (100%) diff --git a/lib/nexpose/ip_range.rb b/lib/nexpose/ip_range.rb index d829aea9..f8ea5532 100644 --- a/lib/nexpose/ip_range.rb +++ b/lib/nexpose/ip_range.rb @@ -140,7 +140,7 @@ def to_s return from.to_s if to.nil? "#{from.to_s} - #{to.to_s}" end - private + private def include_ipaddr?(other) other_range = other.to_range other_from = other_range.first diff --git a/spec/nexpose/site/ip_range_spec.rb b/spec/nexpose/ip_range_spec.rb similarity index 100% rename from spec/nexpose/site/ip_range_spec.rb rename to spec/nexpose/ip_range_spec.rb From 7fe4dc1d4030502ce4420dfc80d1667225807f51 Mon Sep 17 00:00:00 2001 From: Ryan Breed Date: Sat, 8 Oct 2016 17:52:01 -0500 Subject: [PATCH 4/6] factored out shared examples fixing invalid type tests remove test due to class initialization bug --- lib/nexpose/ip_range.rb | 2 +- spec/nexpose/ip_range_spec.rb | 358 +++++++++++++--------------------- 2 files changed, 136 insertions(+), 224 deletions(-) diff --git a/lib/nexpose/ip_range.rb b/lib/nexpose/ip_range.rb index f8ea5532..d000da78 100644 --- a/lib/nexpose/ip_range.rb +++ b/lib/nexpose/ip_range.rb @@ -117,7 +117,7 @@ def include?(other) end include_ipaddr?(other_addr) else - raise ArgumentError, "invalid type: #{other.class.to_s} not one of IPAddr, String, Nexpose::IPRange" + raise ArgumentError, "incompatible type: #{other.class.to_s} not one of IPAddr, String, Nexpose::IPRange" end end diff --git a/spec/nexpose/ip_range_spec.rb b/spec/nexpose/ip_range_spec.rb index 85aabcd5..9378c136 100644 --- a/spec/nexpose/ip_range_spec.rb +++ b/spec/nexpose/ip_range_spec.rb @@ -30,255 +30,167 @@ end end describe '#include?' do - context 'with IPAddr argument' do - let(:ip_range) { Nexpose::IPRange.new('192.168.1.64','192.168.1.127') } - it 'returns a false for valid IPv4 IPAddr outside of range' do - ipv4_outside = IPAddr.new('192.168.1.1') - expect(ip_range.include?(ipv4_outside)).to be_falsey - end - it 'returns a true for valid IPv4 IPAddr inside range' do - ipv4_inside = IPAddr.new('192.168.1.65') - expect(ip_range.include?(ipv4_inside)).to be_truthy + + shared_examples_for 'covered compatible type' do |host_addr| + it 'returns true for IPAddr arguments' do + other = IPAddr.new(host_addr) + expect(iprange.include?(other)).to be_truthy end - it 'returns a false for valid IPv4 network IPAddr outside of range' do - ipv4_network = IPAddr.new('192.168.1.0/29') - expect(ip_range.include?(ipv4_network)).to be_falsey + it 'returns true for Nexpose::IPRange arguments' do + other = Nexpose::IPRange.new(host_addr) + expect(iprange.include?(other)).to be_truthy end - it 'returns a true for valid IPv4 IPAddr inside range' do - ipv4_network = IPAddr.new('192.168.1.64/29') - expect(ip_range.include?(ipv4_network)).to be_truthy + it 'returns true for String arguments' do + other = host_addr + expect(iprange.include?(other)).to be_truthy end end - context 'with a Nexpose::IPRange argument' do - let(:ip_range) { Nexpose::IPRange.new('192.168.1.64','192.168.1.127') } - it 'returns true for single address IPRange bounded by #from and #to' do - other_single_iprange = Nexpose::IPRange.new('192.168.1.65') - expect(ip_range.include?(other_single_iprange)).to be_truthy - end - it 'returns false for single address IPRange not bounded by #from and #to' do - other_single_iprange = Nexpose::IPRange.new('192.168.1.1') - expect(ip_range.include?(other_single_iprange)).to be_falsey + + shared_examples_for 'uncovered compatible type' do |host_addr| + it 'returns false for IPAddr arguments' do + other = IPAddr.new(host_addr) + expect(iprange.include?(other)).to be_falsey end - it 'returns a true for multiple address IPRange bounded by #from and #to' do - other_multiple_iprange = Nexpose::IPRange.new('192.168.1.65', '192.168.1.100') - expect(ip_range.include?(other_multiple_iprange)).to be_truthy + it 'returns false for Nexpose::IPRange arguments' do + other = Nexpose::IPRange.new(host_addr) + expect(iprange.include?(other)).to be_falsey end - it 'returns a false for multiple address IPRange not bounded by #from and #to' do - other_multiple_iprange = Nexpose::IPRange.new('192.168.1.1', '192.168.1.100') - expect(ip_range.include?(other_multiple_iprange)).to be_falsey + it 'returns false for String arguments' do + other = host_addr + expect(iprange.include?(other)).to be_falsey end end - context 'with a castable string argument' do - let(:ip_range) { Nexpose::IPRange.new('192.168.1.64','192.168.1.127') } - it 'returns true for single IPv4 addresses bounded by #from and #to' do - other_host_string = '192.168.1.90' - expect(ip_range.include?(other_host_string)).to be_truthy - end - it 'returns false for single IPv4 addresses not bounded by #from and #to' do - other_host_string = '192.168.1.200' - expect(ip_range.include?(other_host_string)).to be_falsey - end - it 'returns true for masked IPv4 addresses bounded by #from and #to' do - other_cidr_string = '192.168.1.96/29' - expect(ip_range.include?(other_cidr_string)).to be_truthy + + shared_examples_for 'uncovered address' do |other| + it 'returns false' do + expect(iprange.include?(other)).to be_falsey end - it 'returns false for masked IPv4 addresses not bounded by #from and #to' do - other_cidr_string = '192.168.1.0/24' - expect(ip_range.include?(other_cidr_string)).to be_falsey + end + + shared_examples_for 'covered address' do |other| + it 'returns true' do + expect(iprange.include?(other)).to be_truthy end end - context 'with an uncastable argument' do - let(:range) { Nexpose::IPRange.new('192.168.1.64','192.168.1.127') } - it 'returns false for stringlike arguments that cannot be coerced' do - uncastable = 'kitten' - expect(range.include?(uncastable)).to be_falsey + + shared_examples_for 'uncastable string' do |unusable_string| + it 'only works on strings' do + expect(unusable_string).to be_a(String) end - it 'prints warning for stringlike arguments that raise IPAddr::InvalidAddressError' do - uncastable = 'kitten' - expect { range.include?(uncastable) }.to output(/could not coerce/).to_stderr + + it 'returns false' do + expect(iprange.include?(unusable_string)).to be_falsey end - it 'returns false for stringlike arguments that raise IPAddr::AddressFamilyError' do - uncastable = '0' - expect { range.include?(uncastable) }.to output(/could not coerce/).to_stderr + + it 'traps exceptions from IPAddr.initialize' do + expect{ iprange.include?(unusable_string) }.not_to raise_error end - it 'returns false for intlike arguments that cannot be coerced' do - unusable = 0 - expect { range.include?(unusable) }.to raise_error(ArgumentError,/invalid type/) + + it 'emits a warning to stderr' do + expect{ iprange.include?(unusable_string) }.to output(/could not coerce/).to_stderr end end - end - describe '#include_ipaddr?' do - context 'calling from a range of one ip' do - context 'with a /32 argument' do - let(:single) { Nexpose::IPRange.new('192.168.1.1') } - it 'returns false if the other ip is not equal' do - other = IPAddr.new('192.168.1.2') - expect(single.send(:include_ipaddr?,other)).to be_falsey - end - it 'returns true if the other ip is equal' do - other = IPAddr.new('192.168.1.1') - expect(single.send(:include_ipaddr?,other)).to be_truthy - end - end - context 'with a CIDR network argument ' do - let(:single_aligned) { Nexpose::IPRange.new('192.168.1.0') } - it 'returns false if self.from == other base and masklen != 32' do - cidr_28 = IPAddr.new('192.168.1.0/28') - expect(single_aligned.send(:include_ipaddr?,cidr_28)).to be_falsey - end - it 'returns false if self.from != other base and masklen != 32' do - cidr_28_succ = IPAddr.new('192.168.1.1/28') - expect(single_aligned.send(:include_ipaddr?,cidr_28_succ)).to be_falsey - end - - it 'returns true if self.from == other base and masklen == 32' do - cidr_32 = IPAddr.new('192.168.1.0/32') - expect(single_aligned.send(:include_ipaddr?,cidr_32)).to be_truthy - end - it 'returns false if self.from != other base and masklen == 32' do - cidr_32_succ = IPAddr.new('192.168.1.1/32') - expect(single_aligned.send(:include_ipaddr?,cidr_32_succ)).to be_falsey - end - end - context 'with an IPv6 address' do - let(:single) { Nexpose::IPRange.new('192.168.1.1') } - it 'returns false for an IPv6 address' do - ipv6 = IPAddr.new('2001:0:0:0:DB8:800:200C:417A') - expect(single.send(:include_ipaddr?,ipv6)).to be_falsey - end - it 'returns false for an equivalent IPv6 address' do - ipv6_equiv = IPAddr.new('0:0:0:0:0:0:c0a8:0101') - expect(single.send(:include_ipaddr?,ipv6_equiv)).to be_falsey - end - it 'returns false for an ipv4 compatible IPv6 address' do - ipv6_compat = IPAddr.new('192.168.1.1').ipv4_compat - expect(single.send(:include_ipaddr?,ipv6_compat)).to be_falsey - end + + shared_examples_for 'incompatible type' do |other| + it 'raises an ArgumentError' do + expect{ iprange.include?(other) }.to raise_error( ArgumentError, /incompatible type/) end end - context 'calling from a spanning range' do - - context 'with a single IPv4 argument' do - let(:span_10_100) { Nexpose::IPRange.new('192.168.1.10','192.168.1.100') } - it 'returns false if self.to < other' do - outside_right = IPAddr.new('192.168.1.101') - expect(span_10_100.send(:include_ipaddr?,outside_right)).to be_falsey - end - it 'returns false if self.from > other' do - outside_left = IPAddr.new('192.168.1.1') - expect(span_10_100.send(:include_ipaddr?,outside_left)).to be_falsey - end - it 'returns true if self.from==other' do - equal_from = IPAddr.new('192.168.1.10') - expect(span_10_100.send(:include_ipaddr?,equal_from)).to be_truthy - end - it 'returns true if self.to==other' do - equal_to = IPAddr.new('192.168.1.100') - expect(span_10_100.send(:include_ipaddr?,equal_to)).to be_truthy - end - it 'returns true if self.from < other < self.to' do - inside = IPAddr.new('192.168.1.50') - expect(span_10_100.send(:include_ipaddr?,inside)).to be_truthy - end - end - context 'calling from an aligned range' do - it 'returns true if self == other' do - range = Nexpose::IPRange.new('192.168.1.64','192.168.1.127') - same_cidr = IPAddr.new('192.168.1.64/26') - expect(range.send(:include_ipaddr?,same_cidr)).to be_truthy - end - - it 'returns false for uncovered left and uncovered right' do - range = Nexpose::IPRange.new('192.168.1.64','192.168.1.127') - uncovered_cidr = IPAddr.new('192.168.1.0/24') - expect(range.send(:include_ipaddr?,uncovered_cidr)).to be_falsey - end - it 'returns false for uncovered left and equal right' do - range = Nexpose::IPRange.new('192.168.1.64','192.168.1.127') - uncovered_left_equal_right = IPAddr.new('192.168.1.64/25') - expect(range.send(:include_ipaddr?,uncovered_left_equal_right)).to be_falsey - end - it 'returns false for equal left and uncovered right' do - range = Nexpose::IPRange.new('192.168.1.0','192.168.1.127') - equal_left_uncovered_right = IPAddr.new('192.168.1.0/24') - expect(range.send(:include_ipaddr?,equal_left_uncovered_right)).to be_falsey - end - it 'returns false for included left and uncovered right' do - range = Nexpose::IPRange.new('192.168.1.64','192.168.1.95') - included_left_uncovered_right = IPAddr.new('192.168.1.65/26') - expect(range.send(:include_ipaddr?, included_left_uncovered_right)).to be_falsey - end - it 'returns true for included left and equal right' do - range = Nexpose::IPRange.new('192.168.1.64','192.168.1.95') - included_left_equal_right = IPAddr.new('192.168.1.80/28') - expect(range.send(:include_ipaddr?, included_left_equal_right)).to be_truthy - end - it 'returns true for equal left and included right' do - range = Nexpose::IPRange.new('192.168.1.64','192.168.1.95') - equal_left_included_right = IPAddr.new('192.168.1.64/28') - expect(range.send(:include_ipaddr?, equal_left_included_right)).to be_truthy - end - it 'returns true for included left and included right' do - included_left_included_right = IPAddr.new('192.168.1.80/29') - range = Nexpose::IPRange.new('192.168.1.64','192.168.1.95') - expect(range.send(:include_ipaddr?, included_left_included_right)).to be_truthy - end + + context 'when IPRange contains a single address' do + let( :iprange ) { Nexpose::IPRange.new('192.168.1.81') } + + below_subject = '192.168.1.80' + above_subject = '192.168.1.82' + uncovered_cidr = '192.168.1.64/28' + equivalent = '192.168.1.81' + covered_cidr = '192.168.1.81/32' + + it_behaves_like 'covered compatible type', equivalent + + it_behaves_like 'uncovered compatible type', below_subject + it_behaves_like 'uncovered compatible type', above_subject + it_behaves_like 'uncovered compatible type', uncovered_cidr + + it_behaves_like 'covered address', Nexpose::IPRange.new(equivalent, equivalent) + it_behaves_like 'covered address', Nexpose::IPRange.new(equivalent) + it_behaves_like 'covered address', Nexpose::IPRange.new(equivalent, nil) + it_behaves_like 'covered address', covered_cidr + it_behaves_like 'covered address', IPAddr.new(covered_cidr) + + it_behaves_like 'uncovered address', Nexpose::IPRange.new(below_subject, equivalent) + it_behaves_like 'uncovered address', Nexpose::IPRange.new(equivalent, above_subject) + it_behaves_like 'uncovered address', Nexpose::IPRange.new(below_subject, above_subject) + + context 'making invalid comparisons' do + it_behaves_like 'uncastable string', 'kitten' + it_behaves_like 'uncastable string', '0' + it_behaves_like 'incompatible type', 0 + it_behaves_like 'incompatible type', :kitten + it_behaves_like 'incompatible type', nil end end - end - describe '#include_iprange?' do - context 'calling from a single ip' do - let(:single) { Nexpose::IPRange.new('192.168.1.1') } - it 'returns true if self.from == other.from and other.to.nil?' do - other = Nexpose::IPRange.new('192.168.1.1') - expect(single.send(:include_iprange?, other)).to be_truthy - end - it 'returns false for other.from < self.from and other.to.nil?'do - other = Nexpose::IPRange.new('192.168.1.0') - expect(single.send(:include_iprange?, other)).to be_falsey - end - it 'returns false for other.from > self.from and other.to.nil?' do - other = Nexpose::IPRange.new('192.168.1.2') - expect(single.send(:include_iprange?, other)).to be_falsey - end - it 'returns false for self.from == other.from and other.to not nil' do - other = Nexpose::IPRange.new('192.168.1.1','192.168.1.2') - expect(single.send(:include_iprange?, other)).to be_falsey - end - it 'returns false for self.from < other.from and other.to not nil' do - other = Nexpose::IPRange.new('192.168.1.2','192.168.1.3') - expect(single.send(:include_iprange?, other)).to be_falsey - end - it 'returns false for self.from > other.from and other.to not nil' do - other = Nexpose::IPRange.new('192.168.1.0','192.168.1.1') - expect(single.send(:include_iprange?, other)).to be_falsey + + context 'when IPRange spans multiple addresses' do + let( :iprange ) { Nexpose::IPRange.new('192.168.1.64','192.168.1.95') } + + covered_cidr = '192.168.1.72/30' + equivalent_cidr = '192.168.1.64/27' + + lower_bound = '192.168.1.64' + upper_bound = '192.168.1.95' + below_subject = '192.168.1.63' + above_subject = '192.168.1.96' + inside_subject = '192.168.1.65' + + included_cidr_same_right = '192.168.1.88/29' + included_cidr_same_left = '192.168.1.64/29' + uncovered_cidr_same_left = '192.168.1.64/26' + uncovered_cidr = '192.168.1.0/25' + + context 'comparing bounded address' do + it_behaves_like 'covered compatible type', equivalent_cidr + it_behaves_like 'covered compatible type', lower_bound + it_behaves_like 'covered compatible type', upper_bound + it_behaves_like 'covered compatible type', inside_subject end - end - context 'called from a spanning range' do - let(:spanning) { Nexpose::IPRange.new('192.168.1.1','192.168.1.100') } - it 'returns false for other.from < self.from' do - uncovered_left = Nexpose::IPRange.new('192.168.1.0','192.168.1.2') - expect(spanning.send(:include_iprange?, uncovered_left)).to be_falsey + context 'comparing bounded cidr' do + it_behaves_like 'covered compatible type', covered_cidr + it_behaves_like 'covered compatible type', included_cidr_same_left + it_behaves_like 'covered compatible type', included_cidr_same_right end - it 'returns false for self.to < other.to' do - uncovered_right = Nexpose::IPRange.new('192.168.1.2','192.168.1.105') - expect(spanning.send(:include_iprange?, uncovered_right)).to be_falsey + + context 'comparing bounded Nexpose::IPRange' do + it_behaves_like 'covered address', Nexpose::IPRange.new(lower_bound, upper_bound) + it_behaves_like 'covered address', Nexpose::IPRange.new(lower_bound, inside_subject) + it_behaves_like 'covered address', Nexpose::IPRange.new(inside_subject, upper_bound) end - it 'returns true for self.from < other.from && other.to < self.to' do - fully_included = Nexpose::IPRange.new('192.168.1.2','192.168.1.99') - expect(spanning.send(:include_iprange?, fully_included)).to be_truthy + + context 'comparing unbounded address' do + it_behaves_like 'uncovered compatible type', below_subject + it_behaves_like 'uncovered compatible type', above_subject end - it 'returns true for self.from < other.from && other.to == self.to' do - same_right = Nexpose::IPRange.new('192.168.1.2','192.168.1.100') - expect(spanning.send(:include_iprange?, same_right)).to be_truthy + + context 'comparing unbounded cidr' do + it_behaves_like 'uncovered compatible type', uncovered_cidr + it_behaves_like 'uncovered compatible type', uncovered_cidr_same_left end - it 'returns true for self.from == other.from && other.to < self.to' do - same_left = Nexpose::IPRange.new('192.168.1.1','192.168.1.3') - expect(spanning.send(:include_iprange?, same_left)).to be_truthy + + context 'comparing unbounded Nexpose::IPRange' do + it_behaves_like 'uncovered address', Nexpose::IPRange.new(below_subject, lower_bound) + it_behaves_like 'uncovered address', Nexpose::IPRange.new(below_subject, upper_bound) + it_behaves_like 'uncovered address', Nexpose::IPRange.new(below_subject, inside_subject) + it_behaves_like 'uncovered address', Nexpose::IPRange.new(below_subject, above_subject) + it_behaves_like 'uncovered address', Nexpose::IPRange.new(lower_bound, above_subject) + it_behaves_like 'uncovered address', Nexpose::IPRange.new(inside_subject, above_subject) end - it 'returns true for self==other' do - same = Nexpose::IPRange.new('192.168.1.1','192.168.1.100') - expect(spanning.send(:include_iprange?, same)).to be_truthy + + context 'making invalid comparisons' do + it_behaves_like 'uncastable string', 'kitten' + it_behaves_like 'uncastable string', '0' + it_behaves_like 'incompatible type', 0 + it_behaves_like 'incompatible type', :kitten + it_behaves_like 'incompatible type', nil end end end From 9c870b25890335f5534eb481cfcb8daf41be198a Mon Sep 17 00:00:00 2001 From: Ryan Breed Date: Sun, 9 Oct 2016 02:39:51 -0500 Subject: [PATCH 5/6] rubocop style fixes --- lib/nexpose/ip_range.rb | 124 ++++++++++++++++++---------------- spec/nexpose/ip_range_spec.rb | 33 +++++---- 2 files changed, 81 insertions(+), 76 deletions(-) diff --git a/lib/nexpose/ip_range.rb b/lib/nexpose/ip_range.rb index d000da78..f249eecb 100644 --- a/lib/nexpose/ip_range.rb +++ b/lib/nexpose/ip_range.rb @@ -27,17 +27,16 @@ class IPRange # x.x.x.0 and x.x.x.255). # # @return [IPRange] an IP address range of one or more addresses. - def initialize(from, to = nil) - @from = from - @to = to unless from == to - - return unless @to.nil? - - range = IPAddr.new(@from.to_s).to_range - unless range.one? - @from = range.first.to_s - @to = range.last.to_s - end + def initialize(lower, upper = nil) + range = IPAddr.new(lower).to_range + span = range.last.to_i - range.first.to_i + @to = case upper + when nil, lower + span > 0 ? range.last.to_s : nil + else + IPAddr.new(upper).to_s + end + @from = range.first.to_s end # Size of the IP range. The total number of IP addresses represented @@ -46,26 +45,32 @@ def initialize(from, to = nil) # @return [Fixnum] size of the range. # def size - return 1 if @to.nil? - from = IPAddr.new(@from) - to = IPAddr.new(@to) - (from..to).to_a.size + 1 + case @to + when nil + 0 + else + upper_ip.to_i - lower_ip.to_i + end + end + + def single? + (size == 1) end include Comparable def <=>(other) - return 1 unless other.respond_to? :from - from = IPAddr.new(@from) - to = @to.nil? ? from : IPAddr.new(@to) - cf_from = IPAddr.new(other.from) - cf_to = IPAddr.new(other.to.nil? ? other.from : other.to) - if cf_to < from - 1 - elsif to < cf_from - -1 - else # Overlapping - 0 + case other + when Nexpose::IPRange + if other.upper_ip < lower_ip + 1 + elsif upper_ip < other.lower_ip + -1 + else # Overlapping + 0 + end + else + (addr = coerce_address(other)) ? self.<=>(Nexpose::IPRange.new(addr)) : 1 end end @@ -106,18 +111,10 @@ def include?(other) when Nexpose::IPRange include_iprange?(other) when String - begin - other_addr = IPAddr.new(other) - rescue IPAddr::InvalidAddressError => invalid_address - warn "could not coerce \"#{other}\" to IPAddr at #{invalid_address.backtrace[0]}: #{invalid_address.cause.to_s}" - return false - rescue IPAddr::AddressFamilyError => address_family - warn "could not coerce \"#{other}\" to IPAddr at #{address_family.backtrace[0]}: #{address_family.cause.to_s}" - return false - end - include_ipaddr?(other_addr) + other_addr = coerce_address(other) + other_addr ? include_ipaddr?(other_addr) : false else - raise ArgumentError, "incompatible type: #{other.class.to_s} not one of IPAddr, String, Nexpose::IPRange" + raise ArgumentError, "incompatible type: #{other.class} cannot be coerced to IPAddr or Nexpose::IPRange" end end @@ -127,7 +124,7 @@ def hash def as_xml xml = REXML::Element.new('range') - xml.add_attributes({ 'from' => @from, 'to' => @to }) + xml.add_attributes('from' => @from, 'to' => @to) xml end alias_method :to_xml_elem, :as_xml @@ -138,34 +135,43 @@ def to_xml def to_s return from.to_s if to.nil? - "#{from.to_s} - #{to.to_s}" + "#{from} - #{to}" end + + def lower_ip + IPAddr.new(@from) + end + + def upper_ip + @to.nil? ? IPAddr.new(@from) : IPAddr.new(@to) + end + private + + def coerce_address(str) + addr = begin + IPAddr.new(str) + rescue IPAddr::AddressFamilyError, IPAddr::InvalidAddressError => invalid + warn format('could not coerce "%s" to IPAddr: %s', str, invalid.to_s) + false + end + addr + end + def include_ipaddr?(other) - other_range = other.to_range - other_from = other_range.first - other_to = other_range.last - other_iprange = Nexpose::IPRange.new(other_from.to_s, other_to.to_s) - include_iprange?(other_iprange) + ip_range = other.to_range + lower = ip_range.first.to_s + upper = ip_range.last.to_s + nxp_iprange = Nexpose::IPRange.new(lower, upper) + include_iprange?(nxp_iprange) end def include_iprange?(other) - if (other.to==nil) && (self.to==nil) - eql?(other) - elsif (other.to!=nil) && (self.to==nil) - false - elsif (other.to==nil) && (self.to!=nil) - ip_from = IPAddr.new(self.from) - ip_to = IPAddr.new(self.to) - other_from = IPAddr.new(other.from) - (ip_from <= other_from) && (other_from <= ip_to) + if single? + other.single? ? eql?(other) : false else - ip_from = IPAddr.new(self.from) - ip_to = IPAddr.new(self.to) - other_from = IPAddr.new(other.from) - other_to = IPAddr.new(other.to) - (ip_from <= other_from) && (other_to <= ip_to) + (lower_ip <= other.lower_ip) && (other.upper_ip <= upper_ip) end end end -end \ No newline at end of file +end diff --git a/spec/nexpose/ip_range_spec.rb b/spec/nexpose/ip_range_spec.rb index 9378c136..f46d2a4f 100644 --- a/spec/nexpose/ip_range_spec.rb +++ b/spec/nexpose/ip_range_spec.rb @@ -30,7 +30,6 @@ end end describe '#include?' do - shared_examples_for 'covered compatible type' do |host_addr| it 'returns true for IPAddr arguments' do other = IPAddr.new(host_addr) @@ -83,28 +82,28 @@ end it 'traps exceptions from IPAddr.initialize' do - expect{ iprange.include?(unusable_string) }.not_to raise_error + expect { iprange.include?(unusable_string) }.not_to raise_error end it 'emits a warning to stderr' do - expect{ iprange.include?(unusable_string) }.to output(/could not coerce/).to_stderr + expect { iprange.include?(unusable_string) }.to output(/could not coerce/).to_stderr end end shared_examples_for 'incompatible type' do |other| it 'raises an ArgumentError' do - expect{ iprange.include?(other) }.to raise_error( ArgumentError, /incompatible type/) + expect { iprange.include?(other) }.to raise_error(ArgumentError, /incompatible type/) end end context 'when IPRange contains a single address' do - let( :iprange ) { Nexpose::IPRange.new('192.168.1.81') } + let(:iprange) { Nexpose::IPRange.new('192.168.1.81') } - below_subject = '192.168.1.80' - above_subject = '192.168.1.82' + below_subject = '192.168.1.80' + above_subject = '192.168.1.82' uncovered_cidr = '192.168.1.64/28' - equivalent = '192.168.1.81' - covered_cidr = '192.168.1.81/32' + equivalent = '192.168.1.81' + covered_cidr = '192.168.1.81/32' it_behaves_like 'covered compatible type', equivalent @@ -112,16 +111,16 @@ it_behaves_like 'uncovered compatible type', above_subject it_behaves_like 'uncovered compatible type', uncovered_cidr - it_behaves_like 'covered address', Nexpose::IPRange.new(equivalent, equivalent) - it_behaves_like 'covered address', Nexpose::IPRange.new(equivalent) - it_behaves_like 'covered address', Nexpose::IPRange.new(equivalent, nil) - it_behaves_like 'covered address', covered_cidr - it_behaves_like 'covered address', IPAddr.new(covered_cidr) + it_behaves_like 'covered address', Nexpose::IPRange.new(equivalent, equivalent) + it_behaves_like 'covered address', Nexpose::IPRange.new(equivalent) + it_behaves_like 'covered address', Nexpose::IPRange.new(equivalent, nil) + it_behaves_like 'covered address', covered_cidr + it_behaves_like 'covered address', IPAddr.new(covered_cidr) it_behaves_like 'uncovered address', Nexpose::IPRange.new(below_subject, equivalent) - it_behaves_like 'uncovered address', Nexpose::IPRange.new(equivalent, above_subject) + it_behaves_like 'uncovered address', Nexpose::IPRange.new(equivalent, above_subject) it_behaves_like 'uncovered address', Nexpose::IPRange.new(below_subject, above_subject) - + context 'making invalid comparisons' do it_behaves_like 'uncastable string', 'kitten' it_behaves_like 'uncastable string', '0' @@ -132,7 +131,7 @@ end context 'when IPRange spans multiple addresses' do - let( :iprange ) { Nexpose::IPRange.new('192.168.1.64','192.168.1.95') } + let(:iprange) { Nexpose::IPRange.new('192.168.1.64', '192.168.1.95') } covered_cidr = '192.168.1.72/30' equivalent_cidr = '192.168.1.64/27' From d1e4dd75de003c48a649f035a71d0ca9dcd72e9c Mon Sep 17 00:00:00 2001 From: Ryan Breed Date: Sun, 9 Oct 2016 08:25:16 -0500 Subject: [PATCH 6/6] added IncompatibleType exception to be mode explicit about runtime failure --- lib/nexpose/ip_range.rb | 6 +++++- spec/nexpose/ip_range_spec.rb | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/nexpose/ip_range.rb b/lib/nexpose/ip_range.rb index f249eecb..a91ec625 100644 --- a/lib/nexpose/ip_range.rb +++ b/lib/nexpose/ip_range.rb @@ -83,6 +83,8 @@ def eql?(other) @from == other.from && @to == other.to end + IncompatibleType = Class.new(ArgumentError) + # # @overload include?(other) # @param other [IPAddr] /32 IP address @@ -114,7 +116,7 @@ def include?(other) other_addr = coerce_address(other) other_addr ? include_ipaddr?(other_addr) : false else - raise ArgumentError, "incompatible type: #{other.class} cannot be coerced to IPAddr or Nexpose::IPRange" + raise IncompatibleType, "#{other.class} not IPAddr, Nexpose::IPRange, or String" end end @@ -138,6 +140,8 @@ def to_s "#{from} - #{to}" end + protected + def lower_ip IPAddr.new(@from) end diff --git a/spec/nexpose/ip_range_spec.rb b/spec/nexpose/ip_range_spec.rb index f46d2a4f..ecfad296 100644 --- a/spec/nexpose/ip_range_spec.rb +++ b/spec/nexpose/ip_range_spec.rb @@ -92,7 +92,7 @@ shared_examples_for 'incompatible type' do |other| it 'raises an ArgumentError' do - expect { iprange.include?(other) }.to raise_error(ArgumentError, /incompatible type/) + expect { iprange.include?(other) }.to raise_error(Nexpose::IPRange::IncompatibleType, /not IPAddr, Nexpose::IPRange, or String/) end end