mirror of
https://github.com/github/octodns.git
synced 2024-05-11 05:55:00 +00:00
405 lines
13 KiB
Python
405 lines
13 KiB
Python
from unittest import TestCase
|
|
from unittest.mock import MagicMock, call, patch
|
|
|
|
from octodns.processor.spf import (
|
|
SpfDnsLookupException,
|
|
SpfDnsLookupProcessor,
|
|
SpfValueException,
|
|
)
|
|
from octodns.record.base import Record
|
|
from octodns.zone import Zone
|
|
|
|
|
|
class TestSpfDnsLookupProcessor(TestCase):
|
|
def test_get_spf_from_txt_values(self):
|
|
processor = SpfDnsLookupProcessor('test')
|
|
|
|
# Used in logging
|
|
record = Record.new(
|
|
Zone('unit.tests.', []),
|
|
'',
|
|
{'type': 'TXT', 'ttl': 86400, 'values': ['']},
|
|
)
|
|
|
|
self.assertIsNone(
|
|
processor._get_spf_from_txt_values(
|
|
record, ['v=DMARC1\\; p=reject\\;']
|
|
)
|
|
)
|
|
|
|
self.assertEqual(
|
|
'v=spf1 include:example.com ~all',
|
|
processor._get_spf_from_txt_values(
|
|
record,
|
|
['v=DMARC1\\; p=reject\\;', 'v=spf1 include:example.com ~all'],
|
|
),
|
|
)
|
|
|
|
with self.assertRaises(SpfValueException):
|
|
processor._get_spf_from_txt_values(
|
|
record,
|
|
[
|
|
'v=spf1 include:example.com ~all',
|
|
'v=spf1 include:example.com ~all',
|
|
],
|
|
)
|
|
|
|
# Missing "all" or "redirect" at the end
|
|
self.assertEqual(
|
|
'v=spf1 include:example.com',
|
|
processor._get_spf_from_txt_values(
|
|
record,
|
|
['v=spf1 include:example.com', 'v=DMARC1\\; p=reject\\;'],
|
|
),
|
|
)
|
|
|
|
self.assertEqual(
|
|
'v=spf1 +mx redirect=example.com',
|
|
processor._get_spf_from_txt_values(
|
|
record,
|
|
['v=spf1 +mx redirect=example.com', 'v=DMARC1\\; p=reject\\;'],
|
|
),
|
|
)
|
|
|
|
@patch('dns.resolver.resolve')
|
|
def test_processor(self, resolver_mock):
|
|
processor = SpfDnsLookupProcessor('test')
|
|
self.assertEqual('test', processor.name)
|
|
|
|
zone = Zone('unit.tests.', [])
|
|
zone.add_record(
|
|
Record.new(
|
|
zone,
|
|
'',
|
|
{
|
|
'type': 'TXT',
|
|
'ttl': 86400,
|
|
'values': ['v=DMARC1\\; p=reject\\;'],
|
|
},
|
|
)
|
|
)
|
|
zone.add_record(
|
|
Record.new(
|
|
zone, '', {'type': 'A', 'ttl': 86400, 'value': '1.2.3.4'}
|
|
)
|
|
)
|
|
|
|
self.assertEqual(zone, processor.process_source_zone(zone))
|
|
|
|
zone = Zone('unit.tests.', [])
|
|
zone.add_record(
|
|
Record.new(
|
|
zone,
|
|
'',
|
|
{
|
|
'type': 'TXT',
|
|
'ttl': 86400,
|
|
'values': [
|
|
'v=spf1 a include:example.com ~all',
|
|
'v=DMARC1\\; p=reject\\;',
|
|
],
|
|
},
|
|
)
|
|
)
|
|
|
|
resolver_mock.reset_mock(return_value=True, side_effect=True)
|
|
txt_value_mock = MagicMock()
|
|
txt_value_mock.to_text.return_value = '"v=spf1 -all"'
|
|
resolver_mock.return_value = [txt_value_mock]
|
|
|
|
self.assertEqual(zone, processor.process_source_zone(zone))
|
|
resolver_mock.assert_called_once_with('example.com', 'TXT')
|
|
|
|
zone = Zone('unit.tests.', [])
|
|
zone.add_record(
|
|
Record.new(
|
|
zone,
|
|
'',
|
|
{
|
|
'type': 'TXT',
|
|
'ttl': 86400,
|
|
'values': [
|
|
'v=spf1 a ip4:1.2.3.4 ip6:2001:0db8:85a3:0000:0000:8a2e:0370:7334 -all',
|
|
'v=DMARC1\\; p=reject\\;',
|
|
],
|
|
},
|
|
)
|
|
)
|
|
|
|
self.assertEqual(zone, processor.process_source_zone(zone))
|
|
|
|
zone = Zone('unit.tests.', [])
|
|
zone.add_record(
|
|
Record.new(
|
|
zone,
|
|
'',
|
|
{
|
|
'type': 'TXT',
|
|
'ttl': 86400,
|
|
'values': [
|
|
'v=spf1 a mx exists:example.com a a a a a a a a ~all',
|
|
'v=DMARC1\\; p=reject\\;',
|
|
],
|
|
},
|
|
)
|
|
)
|
|
|
|
with self.assertRaises(SpfDnsLookupException):
|
|
processor.process_source_zone(zone)
|
|
|
|
zone = Zone('unit.tests.', [])
|
|
zone.add_record(
|
|
Record.new(
|
|
zone,
|
|
'',
|
|
{
|
|
'type': 'TXT',
|
|
'ttl': 86400,
|
|
'values': [
|
|
'v=spf1 include:example.com -all',
|
|
'v=DMARC1\\; p=reject\\;',
|
|
],
|
|
},
|
|
)
|
|
)
|
|
|
|
resolver_mock.reset_mock(return_value=True, side_effect=True)
|
|
txt_value_mock = MagicMock()
|
|
txt_value_mock.to_text.return_value = (
|
|
'"v=spf1 a a a a a a a a a a a -all"'
|
|
)
|
|
resolver_mock.return_value = [txt_value_mock]
|
|
|
|
with self.assertRaises(SpfDnsLookupException):
|
|
processor.process_source_zone(zone)
|
|
resolver_mock.assert_called_once_with('example.com', 'TXT')
|
|
|
|
zone = Zone('unit.tests.', [])
|
|
zone.add_record(
|
|
Record.new(
|
|
zone,
|
|
'',
|
|
{
|
|
'type': 'TXT',
|
|
'ttl': 86400,
|
|
'values': [
|
|
'v=spf1 include:example.com -all',
|
|
'v=DMARC1\\; p=reject\\;',
|
|
],
|
|
},
|
|
)
|
|
)
|
|
|
|
resolver_mock.reset_mock(return_value=True, side_effect=True)
|
|
txt_value_mock = MagicMock()
|
|
txt_value_mock.to_text.return_value = (
|
|
'"v=spf1 ip4:1.2.3.4" " ip4:4.3.2.1 -all"'
|
|
)
|
|
resolver_mock.return_value = [txt_value_mock]
|
|
|
|
self.assertEqual(zone, processor.process_source_zone(zone))
|
|
resolver_mock.assert_called_once_with('example.com', 'TXT')
|
|
|
|
zone = Zone('unit.tests.', [])
|
|
zone.add_record(
|
|
Record.new(
|
|
zone,
|
|
'',
|
|
{
|
|
'type': 'TXT',
|
|
'ttl': 86400,
|
|
'values': [
|
|
'v=spf1 include:example.com -all',
|
|
'v=DMARC1\\; p=reject\\;',
|
|
],
|
|
},
|
|
)
|
|
)
|
|
|
|
resolver_mock.reset_mock(return_value=True, side_effect=True)
|
|
first_txt_value_mock = MagicMock()
|
|
first_txt_value_mock.to_text.return_value = (
|
|
'"v=spf1 include:_spf.example.com -all"'
|
|
)
|
|
second_txt_value_mock = MagicMock()
|
|
second_txt_value_mock.to_text.return_value = '"v=spf1 a -all"'
|
|
resolver_mock.side_effect = [
|
|
[first_txt_value_mock],
|
|
[second_txt_value_mock],
|
|
]
|
|
|
|
self.assertEqual(zone, processor.process_source_zone(zone))
|
|
resolver_mock.assert_has_calls(
|
|
[call('example.com', 'TXT'), call('_spf.example.com', 'TXT')]
|
|
)
|
|
|
|
def test_processor_with_long_txt_value(self):
|
|
processor = SpfDnsLookupProcessor('test')
|
|
zone = Zone('unit.tests.', [])
|
|
|
|
zone.add_record(
|
|
Record.new(
|
|
zone,
|
|
'',
|
|
{
|
|
'type': 'TXT',
|
|
'ttl': 86400,
|
|
'value': (
|
|
'v=spf1 ip6:2001:0db8:85a3:0000:0000:8a2e:0370:7334 ip6:2001:0db8:85a3:0000:0000:8a2e:0370:7334'
|
|
' ip6:2001:0db8:85a3:0000:0000:8a2e:0370:7334 ip6:2001:0db8:85a3:0000:0000:8a2e:0370:7334'
|
|
' ip6:2001:0db8:85a3:0000:0000:8a2e:0370:7334 ip6:2001:0db8:85a3:0000:0000:8a2e:0370:7334'
|
|
' ip6:2001:0db8:85a3:0000:0000:8a2e:0370:7334 ip6:2001:0db8:85a3:0000:0000:8a2e:0370:7334'
|
|
' ip6:2001:0db8:85a3:0000:0000:8a2e:0370:7334 ip6:2001:0db8:85a3:0000:0000:8a2e:0370:7334'
|
|
' ip6:2001:0db8:85a3:0000:0000:8a2e:0370:7334 ip6:2001:0db8:85a3:0000:0000:8a2e:0370:7334'
|
|
' ip6:2001:0db8:85a3:0000:0000:8a2e:0370:7334 ~all'
|
|
),
|
|
},
|
|
)
|
|
)
|
|
|
|
self.assertEqual(zone, processor.process_source_zone(zone))
|
|
|
|
@patch('dns.resolver.resolve')
|
|
def test_processor_with_lenient_record(self, resolver_mock):
|
|
processor = SpfDnsLookupProcessor('test')
|
|
zone = Zone('unit.tests.', [])
|
|
|
|
lenient = Record.new(
|
|
zone,
|
|
'lenient',
|
|
{
|
|
'type': 'TXT',
|
|
'ttl': 86400,
|
|
'value': 'v=spf1 a a a a a a a a a a a ~all',
|
|
'octodns': {'lenient': True},
|
|
},
|
|
)
|
|
zone.add_record(lenient)
|
|
|
|
self.assertEqual(zone, processor.process_source_zone(zone))
|
|
resolver_mock.assert_not_called()
|
|
|
|
@patch('dns.resolver.resolve')
|
|
def test_processor_errors_on_too_many_spf_values(self, resolver_mock):
|
|
processor = SpfDnsLookupProcessor('test')
|
|
zone = Zone('unit.tests.', [])
|
|
|
|
record = Record.new(
|
|
zone,
|
|
'',
|
|
{
|
|
'type': 'TXT',
|
|
'ttl': 86400,
|
|
'values': [
|
|
'v=spf1 include:_spf.google.com ~all',
|
|
'v=spf1 include:mailgun.org ~all',
|
|
],
|
|
},
|
|
)
|
|
zone.add_record(record)
|
|
|
|
with self.assertRaises(SpfValueException):
|
|
processor.process_source_zone(zone)
|
|
resolver_mock.assert_not_called()
|
|
|
|
@patch('dns.resolver.resolve')
|
|
def test_processor_errors_ptr_mechanisms(self, resolver_mock):
|
|
processor = SpfDnsLookupProcessor('test')
|
|
zone = Zone('unit.tests.', [])
|
|
|
|
zone.add_record(
|
|
Record.new(
|
|
zone,
|
|
'',
|
|
{'type': 'TXT', 'ttl': 86400, 'values': ['v=spf1 ptr ~all']},
|
|
)
|
|
)
|
|
|
|
with self.assertRaises(SpfValueException) as context:
|
|
processor.process_source_zone(zone)
|
|
self.assertEqual(
|
|
'unit.tests. uses the deprecated ptr mechanism',
|
|
str(context.exception),
|
|
)
|
|
resolver_mock.assert_not_called()
|
|
|
|
zone = Zone('unit.tests.', [])
|
|
|
|
zone.add_record(
|
|
Record.new(
|
|
zone,
|
|
'',
|
|
{
|
|
'type': 'TXT',
|
|
'ttl': 86400,
|
|
'values': ['v=spf1 ptr:example.com ~all'],
|
|
},
|
|
)
|
|
)
|
|
|
|
resolver_mock.reset_mock(return_value=True, side_effect=True)
|
|
|
|
with self.assertRaises(SpfValueException) as context:
|
|
processor.process_source_zone(zone)
|
|
self.assertEqual(
|
|
'unit.tests. uses the deprecated ptr mechanism',
|
|
str(context.exception),
|
|
)
|
|
resolver_mock.assert_not_called()
|
|
|
|
zone = Zone('unit.tests.', [])
|
|
|
|
zone.add_record(
|
|
Record.new(
|
|
zone,
|
|
'',
|
|
{
|
|
'type': 'TXT',
|
|
'ttl': 86400,
|
|
'values': ['v=spf1 include:example.com ~all'],
|
|
},
|
|
)
|
|
)
|
|
|
|
resolver_mock.reset_mock(return_value=True, side_effect=True)
|
|
txt_value_mock = MagicMock()
|
|
txt_value_mock.to_text.return_value = '"v=spf1 ptr -all"'
|
|
resolver_mock.return_value = [txt_value_mock]
|
|
|
|
with self.assertRaises(SpfValueException) as context:
|
|
processor.process_source_zone(zone)
|
|
self.assertEqual(
|
|
'unit.tests. uses the deprecated ptr mechanism',
|
|
str(context.exception),
|
|
)
|
|
resolver_mock.assert_called_once_with('example.com', 'TXT')
|
|
|
|
@patch('dns.resolver.resolve')
|
|
def test_processor_errors_on_recursive_include_mechanism(
|
|
self, resolver_mock
|
|
):
|
|
processor = SpfDnsLookupProcessor('test')
|
|
zone = Zone('unit.tests.', [])
|
|
|
|
zone.add_record(
|
|
Record.new(
|
|
zone,
|
|
'',
|
|
{
|
|
'type': 'TXT',
|
|
'ttl': 86400,
|
|
'values': ['v=spf1 include:example.com ~all'],
|
|
},
|
|
)
|
|
)
|
|
|
|
txt_value_mock = MagicMock()
|
|
txt_value_mock.to_text.return_value = (
|
|
'"v=spf1 include:example.com ~all"'
|
|
)
|
|
resolver_mock.return_value = [txt_value_mock]
|
|
|
|
with self.assertRaises(SpfDnsLookupException):
|
|
processor.process_source_zone(zone)
|
|
resolver_mock.assert_called_with('example.com', 'TXT')
|