coppertone 0.0.1 → 0.0.2

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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/LICENSE +1 -1
  4. data/Rakefile +2 -2
  5. data/coppertone.gemspec +1 -0
  6. data/lib/coppertone/directive.rb +14 -7
  7. data/lib/coppertone/error.rb +24 -5
  8. data/lib/coppertone/ip_address_wrapper.rb +9 -6
  9. data/lib/coppertone/macro_context.rb +7 -13
  10. data/lib/coppertone/macro_string/macro_expand.rb +3 -2
  11. data/lib/coppertone/macro_string.rb +12 -5
  12. data/lib/coppertone/mechanism/a.rb +5 -1
  13. data/lib/coppertone/mechanism/all.rb +11 -4
  14. data/lib/coppertone/mechanism/cidr_parser.rb +1 -1
  15. data/lib/coppertone/mechanism/domain_spec_mechanism.rb +9 -0
  16. data/lib/coppertone/mechanism/domain_spec_optional.rb +1 -0
  17. data/lib/coppertone/mechanism/domain_spec_required.rb +1 -0
  18. data/lib/coppertone/mechanism/domain_spec_with_dual_cidr.rb +1 -0
  19. data/lib/coppertone/mechanism/exists.rb +5 -1
  20. data/lib/coppertone/mechanism/include.rb +17 -5
  21. data/lib/coppertone/mechanism/ip4.rb +5 -1
  22. data/lib/coppertone/mechanism/ip6.rb +5 -1
  23. data/lib/coppertone/mechanism/ip_mechanism.rb +9 -1
  24. data/lib/coppertone/mechanism/mx.rb +5 -1
  25. data/lib/coppertone/mechanism/ptr.rb +5 -1
  26. data/lib/coppertone/mechanism.rb +25 -2
  27. data/lib/coppertone/modifier/base.rb +1 -0
  28. data/lib/coppertone/modifier/exp.rb +6 -2
  29. data/lib/coppertone/modifier/redirect.rb +5 -1
  30. data/lib/coppertone/modifier/unknown.rb +6 -1
  31. data/lib/coppertone/modifier.rb +11 -2
  32. data/lib/coppertone/null_macro_context.rb +23 -0
  33. data/lib/coppertone/qualifier.rb +8 -0
  34. data/lib/coppertone/record.rb +33 -41
  35. data/lib/coppertone/record_finder.rb +3 -1
  36. data/lib/coppertone/record_term_parser.rb +30 -0
  37. data/lib/coppertone/request.rb +3 -1
  38. data/lib/coppertone/request_context.rb +1 -2
  39. data/lib/coppertone/result.rb +6 -2
  40. data/lib/coppertone/utils/validated_domain_finder.rb +6 -4
  41. data/lib/coppertone/version.rb +1 -1
  42. data/lib/coppertone.rb +3 -1
  43. data/spec/directive_spec.rb +13 -0
  44. data/spec/ip_address_wrapper_spec.rb +3 -0
  45. data/spec/macro_string_spec.rb +20 -0
  46. data/spec/mechanism/a_spec.rb +22 -7
  47. data/spec/mechanism/all_spec.rb +8 -0
  48. data/spec/mechanism/exists_spec.rb +14 -6
  49. data/spec/mechanism/include_spec.rb +13 -1
  50. data/spec/mechanism/ip4_spec.rb +15 -5
  51. data/spec/mechanism/ip6_spec.rb +18 -11
  52. data/spec/mechanism/mx_spec.rb +56 -0
  53. data/spec/mechanism/ptr_spec.rb +7 -0
  54. data/spec/modifier/exp_spec.rb +10 -0
  55. data/spec/modifier/redirect_spec.rb +10 -0
  56. data/spec/open_spf/ALL_mechanism_syntax_spec.rb +6 -6
  57. data/spec/open_spf/A_mechanism_syntax_spec.rb +30 -30
  58. data/spec/open_spf/EXISTS_mechanism_syntax_spec.rb +8 -8
  59. data/spec/open_spf/IP4_mechanism_syntax_spec.rb +10 -10
  60. data/spec/open_spf/IP6_mechanism_syntax_spec.rb +10 -10
  61. data/spec/open_spf/Include_mechanism_semantics_and_syntax_spec.rb +10 -10
  62. data/spec/open_spf/Initial_processing_spec.rb +21 -14
  63. data/spec/open_spf/MX_mechanism_syntax_spec.rb +22 -22
  64. data/spec/open_spf/Macro_expansion_rules_spec.rb +26 -30
  65. data/spec/open_spf/PTR_mechanism_syntax_spec.rb +7 -7
  66. data/spec/open_spf/Processing_limits_spec.rb +12 -12
  67. data/spec/open_spf/Record_evaluation_spec.rb +13 -13
  68. data/spec/open_spf/Record_lookup_spec.rb +8 -8
  69. data/spec/open_spf/Selecting_records_spec.rb +11 -11
  70. data/spec/open_spf/Semantics_of_exp_and_other_modifiers_spec.rb +27 -25
  71. data/spec/open_spf/Test_cases_from_implementation_bugs_spec.rb +2 -2
  72. data/spec/record_spec.rb +34 -13
  73. data/spec/request_context_spec.rb +1 -1
  74. data/spec/rfc7208-tests.yml +10 -0
  75. metadata +59 -48
  76. data/lib/coppertone/dns/error.rb +0 -9
  77. data/lib/coppertone/dns/mock_client.rb +0 -106
  78. data/lib/coppertone/dns/resolv_client.rb +0 -110
  79. data/lib/coppertone/dns.rb +0 -3
  80. data/lib/resolv/dns/resource/in/spf.rb +0 -15
  81. data/spec/dns/resolv_client_spec.rb +0 -307
@@ -27,6 +27,14 @@ module Coppertone
27
27
  @result_code = result_code
28
28
  end
29
29
 
30
+ def default?
31
+ text == DEFAULT_QUALIFIER_TEXT
32
+ end
33
+
34
+ def to_s
35
+ text
36
+ end
37
+
30
38
  PASS = new(DEFAULT_QUALIFIER_TEXT, Result::PASS)
31
39
  FAIL = new('-'.freeze, Result::FAIL)
32
40
  SOFTFAIL = new('~'.freeze, Result::SOFTFAIL)
@@ -2,51 +2,17 @@ module Coppertone
2
2
  # Represents an SPF record. Includes class level methods for parsing
3
3
  # record from a text string.
4
4
  class Record
5
- VERSION_STR = 'v=spf1'
6
- RECORD_REGEXP = /\A#{VERSION_STR}(\s|\z)/i
7
- ALLOWED_CHARACTERS = /\A([\x21-\x7e ]+)\z/
8
-
9
5
  attr_reader :text
10
6
  def initialize(raw_text)
11
- fail RecordParsingError if raw_text.blank?
12
- fail RecordParsingError unless self.class.record?(raw_text)
13
- fail RecordParsingError unless ALLOWED_CHARACTERS.match(raw_text)
14
- @text = raw_text.dup
15
- validate_and_parse
7
+ @terms = Coppertone::RecordTermParser.new(raw_text).terms
8
+ normalize_terms
16
9
  end
17
10
 
18
11
  def self.record?(record_text)
19
- return false if record_text.blank?
20
- RECORD_REGEXP.match(record_text.strip) ? true : false
21
- end
22
-
23
- def self.parse(text)
24
- return nil unless record?(text)
25
- new(text)
26
- end
27
-
28
- def validate_and_parse
29
- text_without_prefix = text[VERSION_STR.length..-1]
30
- @term_tokens = text_without_prefix.strip.split(/ /)
31
- parse_terms
32
- end
33
-
34
- def parse_terms
35
- @terms = []
36
- @term_tokens.each do |token|
37
- term = Term.build_from_token(token)
38
- fail RecordParsingError,
39
- "Could not parse record with #{text}" unless term
40
- @terms << term
41
- end
42
- normalize_terms
12
+ Coppertone::RecordTermParser.record?(record_text)
43
13
  end
44
14
 
45
15
  def normalize_terms
46
- # Discard any redirects if there is a directive with an
47
- # all mechanism present
48
- # Section 6.1
49
- # TODO: PMG
50
16
  find_redirect # Checks for duplicate redirect modifiers
51
17
  exp # Checks for duplicate exp modifiers
52
18
  end
@@ -55,8 +21,21 @@ module Coppertone
55
21
  @directives ||= @terms.select { |t| t.is_a?(Coppertone::Directive) }
56
22
  end
57
23
 
24
+ def all_directive
25
+ @all_directive ||= directives.find(&:all?)
26
+ end
27
+
58
28
  def include_all?
59
- directives.any? { |d| d.mechanism.is_a?(Coppertone::Mechanism::All) }
29
+ all_directive ? true : false
30
+ end
31
+
32
+ def default_result
33
+ return Result.neutral unless all_directive
34
+ Result.from_directive(all_directive)
35
+ end
36
+
37
+ def safe_to_include?
38
+ include_all?
60
39
  end
61
40
 
62
41
  def modifiers
@@ -68,8 +47,6 @@ module Coppertone
68
47
  end
69
48
 
70
49
  def redirect
71
- # Ignore if an 'all' modifier is present
72
- return nil if include_all?
73
50
  @redirect ||= find_redirect
74
51
  end
75
52
 
@@ -77,10 +54,25 @@ module Coppertone
77
54
  @exp ||= find_modifier(Coppertone::Modifier::Exp)
78
55
  end
79
56
 
57
+ KNOWN_MODS =
58
+ [Coppertone::Modifier::Exp, Coppertone::Modifier::Redirect]
59
+ def unknown_modifiers
60
+ @unknown_modifiers ||=
61
+ modifiers.select { |m| KNOWN_MODS.select { |k| m.is_a?(k) }.empty? }
62
+ end
63
+
80
64
  def find_modifier(klass)
81
65
  arr = modifiers.select { |m| m.is_a?(klass) }
82
- fail RecordParsingError if arr.size > 1
66
+ fail DuplicateModifierError if arr.size > 1
83
67
  arr.first
84
68
  end
69
+
70
+ def self.version_str
71
+ Coppertone::RecordTermParser::VERSION_STR
72
+ end
73
+
74
+ def to_s
75
+ "#{self.class.version_str} #{@terms.map(&:to_s).join(' ')}"
76
+ end
85
77
  end
86
78
  end
@@ -11,7 +11,9 @@ module Coppertone
11
11
  @record ||=
12
12
  begin
13
13
  validate_txt_records
14
- Record.parse(txt_records.first)
14
+ spf_dns_record = txt_records.first
15
+ return nil unless spf_dns_record
16
+ Record.new(spf_dns_record)
15
17
  end
16
18
  end
17
19
 
@@ -0,0 +1,30 @@
1
+ module Coppertone
2
+ class RecordTermParser
3
+ VERSION_STR = 'v=spf1'
4
+ RECORD_REGEXP = /\A#{VERSION_STR}(\s|\z)/i
5
+ ALLOWED_CHARACTERS = /\A([\x21-\x7e ]+)\z/
6
+
7
+ def self.record?(text)
8
+ return false if text.blank?
9
+ RECORD_REGEXP.match(text.strip) ? true : false
10
+ end
11
+
12
+ attr_reader :terms
13
+ def initialize(text)
14
+ fail RecordParsingError unless self.class.record?(text)
15
+ fail RecordParsingError unless ALLOWED_CHARACTERS.match(text)
16
+ @terms = term_tokens(text).map { |token| parse_token(token) }
17
+ end
18
+
19
+ def term_tokens(text)
20
+ text_without_prefix = text[VERSION_STR.length..-1]
21
+ text_without_prefix.strip.split(/ /).select { |s| !s.blank? }
22
+ end
23
+
24
+ def parse_token(token)
25
+ term = Term.build_from_token(token)
26
+ fail RecordParsingError unless term
27
+ term
28
+ end
29
+ end
30
+ end
@@ -17,7 +17,7 @@ module Coppertone
17
17
  check_spf_for_mailfrom
18
18
  return mailfrom_result if mailfrom_result && !mailfrom_result.none?
19
19
 
20
- no_matching_record? ? Result.none : Result.new(:neutral)
20
+ no_matching_record? ? Result.none : Result.neutral
21
21
  end
22
22
 
23
23
  def no_matching_record?
@@ -35,6 +35,8 @@ module Coppertone
35
35
  def check_spf_for_context(macro_context, identity)
36
36
  record = spf_record(macro_context)
37
37
  @result = spf_request(macro_context, record, identity) if record
38
+ rescue DNSAdapter::Error => e
39
+ Result.temperror(e.message)
38
40
  rescue Coppertone::TemperrorError => e
39
41
  Result.temperror(e.message)
40
42
  rescue Coppertone::PermerrorError => e
@@ -1,5 +1,4 @@
1
1
  require 'active_support/core_ext/module/delegation'
2
- require 'coppertone/dns/resolv_client'
3
2
 
4
3
  module Coppertone
5
4
  # A container for information that should span the lifetime of
@@ -41,7 +40,7 @@ module Coppertone
41
40
  elsif Coppertone.config.dns_client_class
42
41
  Coppertone.config.dns_client_class.new
43
42
  else
44
- Coppertone::DNS::ResolvClient.new
43
+ DNSAdapter::ResolvClient.new
45
44
  end
46
45
  end
47
46
 
@@ -21,8 +21,8 @@ module Coppertone
21
21
  @mechanism = mechanism
22
22
  end
23
23
 
24
- def self.from_directive(directive, code)
25
- new(code, directive.mechanism)
24
+ def self.from_directive(directive)
25
+ new(directive.qualifier.result_code, directive.mechanism)
26
26
  end
27
27
 
28
28
  def self.permerror(message)
@@ -41,6 +41,10 @@ module Coppertone
41
41
  Result.new(:none)
42
42
  end
43
43
 
44
+ def self.neutral
45
+ Result.new(:neutral)
46
+ end
47
+
44
48
  %w(none pass fail softfail neutral temperror permerror).each do |t|
45
49
  define_method("#{t}?") do
46
50
  self.class.const_get(t.upcase) == send(:code)
@@ -3,9 +3,11 @@ module Coppertone
3
3
  # A class used to find validated domains as defined in
4
4
  # section 5.5 of the RFC.
5
5
  class ValidatedDomainFinder
6
- def initialize(macro_context, request_context)
6
+ attr_reader :subdomain_only
7
+ def initialize(macro_context, request_context, subdomain_only = true)
7
8
  @mc = macro_context
8
9
  @request_context = request_context
10
+ @subdomain_only = subdomain_only
9
11
  end
10
12
 
11
13
  def find(target_name)
@@ -27,10 +29,10 @@ module Coppertone
27
29
 
28
30
  def ptr_record_matches?(ip_checker,
29
31
  target_name, ptr_name)
30
- is_candidate =
31
- DomainUtils.subdomain_or_same?(ptr_name, target_name)
32
+ is_candidate = !subdomain_only ||
33
+ DomainUtils.subdomain_or_same?(ptr_name, target_name)
32
34
  is_candidate && ip_checker.check(ptr_name)
33
- rescue Coppertone::DNS::Error
35
+ rescue DNSAdapter::Error
34
36
  # If a DNS error occurs when looking up a domain, treat it
35
37
  # as a non match
36
38
  false
@@ -1,3 +1,3 @@
1
1
  module Coppertone # rubocop:disable Style/Documentation
2
- VERSION = '0.0.1'.freeze
2
+ VERSION = '0.0.2'.freeze
3
3
  end
data/lib/coppertone.rb CHANGED
@@ -27,20 +27,22 @@ module Coppertone
27
27
  end
28
28
  end
29
29
 
30
+ require 'dns_adapter'
30
31
  require 'coppertone/version'
31
32
  require 'coppertone/utils'
32
33
  require 'coppertone/error'
33
34
  require 'coppertone/request_count_limiter'
34
35
  require 'coppertone/sender_identity'
35
- require 'coppertone/dns'
36
36
  require 'coppertone/ip_address_wrapper'
37
37
  require 'coppertone/macro_context'
38
+ require 'coppertone/null_macro_context'
38
39
  require 'coppertone/macro_string'
39
40
  require 'coppertone/request_context'
40
41
  require 'coppertone/domain_spec'
41
42
  require 'coppertone/directive'
42
43
  require 'coppertone/modifier'
43
44
  require 'coppertone/term'
45
+ require 'coppertone/record_term_parser'
44
46
  require 'coppertone/record'
45
47
  require 'coppertone/record_evaluator'
46
48
  require 'coppertone/record_finder'
@@ -28,6 +28,19 @@ describe Coppertone::Directive do
28
28
  end
29
29
  end
30
30
 
31
+ context 'all?' do
32
+ it 'should be true when the directive is an all' do
33
+ expect(Coppertone::Directive.matching_term('+all')).to be_all
34
+ expect(Coppertone::Directive.matching_term('-all')).to be_all
35
+ expect(Coppertone::Directive.matching_term('~all')).to be_all
36
+ expect(Coppertone::Directive.matching_term('?all')).to be_all
37
+ end
38
+
39
+ it 'should be false otherwise' do
40
+ expect(Coppertone::Directive.matching_term('ip4:1.2.3.4')).not_to be_all
41
+ end
42
+ end
43
+
31
44
  context '.evaluate' do
32
45
  Coppertone::Qualifier.qualifiers.each do |q|
33
46
  it "returns a result with the expected code when it matches #{q.text}" do
@@ -22,6 +22,7 @@ describe Coppertone::IPAddressWrapper do
22
22
  expect(ipw.c).to eq(ip_as_s)
23
23
  expect(ipw.i).to eq(ip_as_s)
24
24
  expect(ipw.v).to eq('in-addr')
25
+ expect(ipw.to_s).to eq(ip_as_s)
25
26
  end
26
27
 
27
28
  it 'should raise an ArgumentError when passed an IP with a prefix' do
@@ -42,6 +43,7 @@ describe Coppertone::IPAddressWrapper do
42
43
  'F.E.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.2.B.3.F.F.F.E.1.E.8.3.2.9'
43
44
  expect(ipw.i).to eq(dotted)
44
45
  expect(ipw.v).to eq('ip6')
46
+ expect(ipw.to_s).to eq(ip_as_s)
45
47
  end
46
48
  end
47
49
 
@@ -55,6 +57,7 @@ describe Coppertone::IPAddressWrapper do
55
57
  expect(ipw.c).to eq(ip_v4_as_s)
56
58
  expect(ipw.i).to eq(ip_v4_as_s)
57
59
  expect(ipw.v).to eq('in-addr')
60
+ expect(ipw.to_s).to eq(ip_as_s)
58
61
  end
59
62
  end
60
63
 
@@ -17,4 +17,24 @@ describe Coppertone::MacroString do
17
17
  .to eql(false)
18
18
  end
19
19
  end
20
+
21
+ context '#context_dependent?' do
22
+ it 'should return true when the macro string contains a macro' do
23
+ strs = ['abc.%{dr-}.%%.test.com', '%{l}.test.com', '%{d}', 'test.%{d}']
24
+ strs.each do |s|
25
+ macro_string = Coppertone::MacroString.new(s)
26
+ expect(macro_string).to be_context_dependent
27
+ expect(macro_string.to_s).to eq(s)
28
+ end
29
+ end
30
+
31
+ it 'should return false when the macro string does not contain a macro' do
32
+ strs = ['abc.%%.test.com', 'test.example.com', '%_', 'test']
33
+ strs.each do |s|
34
+ macro_string = Coppertone::MacroString.new(s)
35
+ expect(macro_string).not_to be_context_dependent
36
+ expect(macro_string.to_s).to eq(s)
37
+ end
38
+ end
39
+ end
20
40
  end
@@ -8,6 +8,7 @@ describe Coppertone::Mechanism::A do
8
8
  expect(mech.domain_spec).to be_nil
9
9
  expect(mech.ip_v4_cidr_length).to eq(32)
10
10
  expect(mech.ip_v6_cidr_length).to eq(128)
11
+ expect(mech.to_s).to eq('a')
11
12
  end
12
13
 
13
14
  it 'should not fail if called with a blank argument' do
@@ -16,6 +17,7 @@ describe Coppertone::Mechanism::A do
16
17
  expect(mech.domain_spec).to be_nil
17
18
  expect(mech.ip_v4_cidr_length).to eq(32)
18
19
  expect(mech.ip_v6_cidr_length).to eq(128)
20
+ expect(mech.to_s).to eq('a')
19
21
  end
20
22
 
21
23
  it 'should fail if called with an invalid macrostring' do
@@ -31,6 +33,7 @@ describe Coppertone::Mechanism::A do
31
33
  .to eq(Coppertone::DomainSpec.new('_spf.%{d}.example.com'))
32
34
  expect(mech.ip_v4_cidr_length).to eq(32)
33
35
  expect(mech.ip_v6_cidr_length).to eq(128)
36
+ expect(mech.to_s).to eq('a:_spf.%{d}.example.com')
34
37
  end
35
38
 
36
39
  it 'should parse a valid IP v4 CIDR length with a domain spec' do
@@ -38,8 +41,9 @@ describe Coppertone::Mechanism::A do
38
41
  expect(mech).not_to be_nil
39
42
  expect(mech.domain_spec)
40
43
  .to eq(Coppertone::DomainSpec.new('_spf.%{d}.example.com'))
41
- expect(mech.ip_v4_cidr_length).to eq('28')
44
+ expect(mech.ip_v4_cidr_length).to eq(28)
42
45
  expect(mech.ip_v6_cidr_length).to eq(128)
46
+ expect(mech.to_s).to eq('a:_spf.%{d}.example.com/28')
43
47
  end
44
48
 
45
49
  it 'should fail if called with an invalid macrostring and IPv4 CIDR' do
@@ -52,8 +56,9 @@ describe Coppertone::Mechanism::A do
52
56
  mech = Coppertone::Mechanism::A.new('/28')
53
57
  expect(mech).not_to be_nil
54
58
  expect(mech.domain_spec).to be_nil
55
- expect(mech.ip_v4_cidr_length).to eq('28')
59
+ expect(mech.ip_v4_cidr_length).to eq(28)
56
60
  expect(mech.ip_v6_cidr_length).to eq(128)
61
+ expect(mech.to_s).to eq('a/28')
57
62
  end
58
63
 
59
64
  it 'should not parse an invalid IP v4 CIDR length' do
@@ -72,7 +77,8 @@ describe Coppertone::Mechanism::A do
72
77
  expect(mech.domain_spec)
73
78
  .to eq(Coppertone::DomainSpec.new('_spf.%{d}.example.com'))
74
79
  expect(mech.ip_v4_cidr_length).to eq(32)
75
- expect(mech.ip_v6_cidr_length).to eq('64')
80
+ expect(mech.ip_v6_cidr_length).to eq(64)
81
+ expect(mech.to_s).to eq('a:_spf.%{d}.example.com//64')
76
82
  end
77
83
 
78
84
  it 'should fail if called with an invalid macrostring and IPv6 CIDR' do
@@ -86,7 +92,8 @@ describe Coppertone::Mechanism::A do
86
92
  expect(mech).not_to be_nil
87
93
  expect(mech.domain_spec).to be_nil
88
94
  expect(mech.ip_v4_cidr_length).to eq(32)
89
- expect(mech.ip_v6_cidr_length).to eq('64')
95
+ expect(mech.ip_v6_cidr_length).to eq(64)
96
+ expect(mech.to_s).to eq('a//64')
90
97
  end
91
98
 
92
99
  it 'should not parse an invalid IP v6 CIDR length' do
@@ -104,8 +111,9 @@ describe Coppertone::Mechanism::A do
104
111
  expect(mech).not_to be_nil
105
112
  expect(mech.domain_spec)
106
113
  .to eq(Coppertone::DomainSpec.new('_spf.%{d}.example.com'))
107
- expect(mech.ip_v4_cidr_length).to eq('28')
108
- expect(mech.ip_v6_cidr_length).to eq('64')
114
+ expect(mech.ip_v4_cidr_length).to eq(28)
115
+ expect(mech.ip_v6_cidr_length).to eq(64)
116
+ expect(mech.to_s).to eq('a:_spf.%{d}.example.com/28//64')
109
117
  end
110
118
 
111
119
  it 'should not parse a invalid dual CIDR length with a domain spec' do
@@ -178,7 +186,7 @@ describe Coppertone::Mechanism::A do
178
186
  end
179
187
 
180
188
  before do
181
- allow(Coppertone::DNS::ResolvClient)
189
+ allow(DNSAdapter::ResolvClient)
182
190
  .to receive(:new).and_return(gmail_dns_client)
183
191
  end
184
192
 
@@ -195,4 +203,11 @@ describe Coppertone::Mechanism::A do
195
203
  end
196
204
  end
197
205
  end
206
+
207
+ context 'dns_lookup_term?' do
208
+ it 'should be true' do
209
+ expect(Coppertone::Mechanism::A).to be_dns_lookup_term
210
+ expect(Coppertone::Mechanism::A.new('//64')).to be_dns_lookup_term
211
+ end
212
+ end
198
213
  end
@@ -4,6 +4,7 @@ describe Coppertone::Mechanism::All do
4
4
  subject { Coppertone::Mechanism::All.instance }
5
5
  it 'should always return true regardless of argument' do
6
6
  expect(subject.match?(double, double)).to eq(true)
7
+ expect(subject.to_s).to eq('all')
7
8
  end
8
9
 
9
10
  it 'should not allow creation of new instances' do
@@ -19,4 +20,11 @@ describe Coppertone::Mechanism::All do
19
20
  end.to raise_error(Coppertone::RecordParsingError)
20
21
  end
21
22
  end
23
+
24
+ context 'dns_lookup_term?' do
25
+ it 'should be false' do
26
+ expect(Coppertone::Mechanism::All).not_to be_dns_lookup_term
27
+ expect(Coppertone::Mechanism::All.instance).not_to be_dns_lookup_term
28
+ end
29
+ end
22
30
  end
@@ -19,6 +19,11 @@ describe Coppertone::Mechanism::Exists do
19
19
  Coppertone::Mechanism::Exists.new(':abc%:def')
20
20
  end.to raise_error(Coppertone::InvalidMechanismError)
21
21
  end
22
+
23
+ it 'should succeed if called with a valid macrostring' do
24
+ mech = Coppertone::Mechanism::Exists.new(':%{d}.example.com')
25
+ expect(mech.to_s).to eq('exists:%{d}.example.com')
26
+ end
22
27
  end
23
28
 
24
29
  context '#create' do
@@ -66,12 +71,8 @@ describe Coppertone::Mechanism::Exists do
66
71
  Coppertone::MacroContext.new(domain, '74.125.239.118', 'bob@gmail.com')
67
72
  end
68
73
 
69
- let(:not_matching_context) do
70
- Coppertone::MacroContext.new(domain, '74.125.249.118', 'bob@gmail.com')
71
- end
72
-
73
74
  before do
74
- allow(Coppertone::DNS::ResolvClient)
75
+ allow(DNSAdapter::ResolvClient)
75
76
  .to receive(:new).and_return(dns_client)
76
77
  end
77
78
 
@@ -81,11 +82,18 @@ describe Coppertone::Mechanism::Exists do
81
82
  .to eq(true)
82
83
  end
83
84
 
84
- it 'should match when the domain record for the target name exists' do
85
+ it 'should not match when the domain record for the target name does not exist' do
85
86
  mech = Coppertone::Mechanism::Exists.create(bad_arg)
86
87
  expect(mech.match?(matching_context, Coppertone::RequestContext.new))
87
88
  .to eq(false)
88
89
  end
89
90
  end
90
91
  end
92
+
93
+ context 'dns_lookup_term?' do
94
+ it 'should be true' do
95
+ expect(Coppertone::Mechanism::Exists).to be_dns_lookup_term
96
+ expect(Coppertone::Mechanism::Exists.new(':example.com')).to be_dns_lookup_term
97
+ end
98
+ end
91
99
  end
@@ -16,9 +16,14 @@ describe Coppertone::Mechanism::Include do
16
16
 
17
17
  it 'should fail if called with an invalid macrostring' do
18
18
  expect do
19
- Coppertone::Mechanism::Include.new('abc%:def')
19
+ Coppertone::Mechanism::Include.new(':abc%:def')
20
20
  end.to raise_error(Coppertone::InvalidMechanismError)
21
21
  end
22
+
23
+ it 'creates a mechanism if called with a valid macrostring' do
24
+ mech = Coppertone::Mechanism::Include.new(':_spf.example.com')
25
+ expect(mech.to_s).to eq('include:_spf.example.com')
26
+ end
22
27
  end
23
28
 
24
29
  context '#create' do
@@ -40,4 +45,11 @@ describe Coppertone::Mechanism::Include do
40
45
  end.to raise_error(Coppertone::InvalidMechanismError)
41
46
  end
42
47
  end
48
+
49
+ context 'dns_lookup_term?' do
50
+ it 'should be true' do
51
+ expect(Coppertone::Mechanism::Include).to be_dns_lookup_term
52
+ expect(Coppertone::Mechanism::Include.new(':example.com')).to be_dns_lookup_term
53
+ end
54
+ end
43
55
  end
@@ -23,16 +23,19 @@ describe Coppertone::Mechanism::IP4 do
23
23
  it 'should not fail if called with an IP v6' do
24
24
  mech = Coppertone::Mechanism::IP4.new(':fe80::202:b3ff:fe1e:8329')
25
25
  expect(mech.ip_network).to eq(IPAddr.new('fe80::202:b3ff:fe1e:8329'))
26
+ expect(mech.to_s).to eq('ip4:fe80::202:b3ff:fe1e:8329')
26
27
  end
27
28
 
28
29
  it 'should work if called with an IP4' do
29
30
  mech = Coppertone::Mechanism::IP4.new(':1.2.3.4')
30
31
  expect(mech.ip_network).to eq(IPAddr.new('1.2.3.4'))
32
+ expect(mech.to_s).to eq('ip4:1.2.3.4')
31
33
  end
32
34
 
33
35
  it 'should work if called with an IP4 with a pfxlen' do
34
36
  mech = Coppertone::Mechanism::IP4.new(':1.2.3.4/4')
35
37
  expect(mech.ip_network).to eq(IPAddr.new('1.2.3.4/4'))
38
+ expect(mech.to_s).to eq('ip4:1.2.3.4/4')
36
39
  end
37
40
  end
38
41
 
@@ -66,13 +69,13 @@ describe Coppertone::Mechanism::IP4 do
66
69
  end
67
70
 
68
71
  it 'should work if called with an IP4 with a pfxlen' do
69
- mech = Coppertone::Mechanism::IP4.create('1.2.3.4/4')
72
+ mech = Coppertone::Mechanism::IP4.create(':1.2.3.4/4')
70
73
  expect(mech.ip_network).to eq(IPAddr.new('1.2.3.4/4'))
71
74
  end
72
75
 
73
76
  it 'should fail if called with an invalid pfxlen' do
74
77
  expect do
75
- Coppertone::Mechanism::IP4.new('1.2.3.4/127')
78
+ Coppertone::Mechanism::IP4.new(':1.2.3.4/127')
76
79
  end.to raise_error(Coppertone::InvalidMechanismError)
77
80
  end
78
81
  end
@@ -93,18 +96,25 @@ describe Coppertone::Mechanism::IP4 do
93
96
  end
94
97
 
95
98
  it 'should return true if the client IP is in the network' do
96
- mech = Coppertone::Mechanism::IP4.create('4.5.6.0/29')
99
+ mech = Coppertone::Mechanism::IP4.create(':4.5.6.0/29')
97
100
  expect(mech.match?(macro_context, double)).to eq(true)
98
101
  end
99
102
 
100
103
  it 'should return false if the client IP is not in the network' do
101
- mech = Coppertone::Mechanism::IP4.create('4.5.6.0/30')
104
+ mech = Coppertone::Mechanism::IP4.create(':4.5.6.0/30')
102
105
  expect(mech.match?(macro_context, double)).to eq(false)
103
106
  end
104
107
 
105
108
  it 'should return false if the client IP is v6 only' do
106
- mech = Coppertone::Mechanism::IP4.create('4.5.6.0/29')
109
+ mech = Coppertone::Mechanism::IP4.create(':4.5.6.0/29')
107
110
  expect(mech.match?(ip_v6_macro_context, double)).to eq(false)
108
111
  end
109
112
  end
113
+
114
+ context 'dns_lookup_term?' do
115
+ it 'should be false' do
116
+ expect(Coppertone::Mechanism::IP4).not_to be_dns_lookup_term
117
+ expect(Coppertone::Mechanism::IP4.create(':4.5.6.7')).not_to be_dns_lookup_term
118
+ end
119
+ end
110
120
  end