DMARCReporting 0.2.1__tar.gz → 0.3.1__tar.gz

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 (30) hide show
  1. {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/DMARCReporting/__init__.py +1 -1
  2. {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/DMARCReporting/cli.py +9 -8
  3. {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/DMARCReporting/constants.py +2 -0
  4. {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/DMARCReporting/decompressor.py +14 -2
  5. DMARCReporting-0.2.1/DMARCReporting/FileLister.py → dmarcreporting-0.3.1/DMARCReporting/filelister.py +1 -2
  6. {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/DMARCReporting/parser.py +17 -0
  7. {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/DMARCReporting.egg-info/PKG-INFO +13 -2
  8. {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/DMARCReporting.egg-info/SOURCES.txt +2 -2
  9. {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/PKG-INFO +13 -2
  10. {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/setup.py +3 -1
  11. dmarcreporting-0.3.1/tests/test_acceptance.py +122 -0
  12. {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/tests/test_console_renderer.py +5 -3
  13. {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/tests/test_csv_renderer.py +5 -3
  14. {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/tests/test_decompressor.py +7 -1
  15. DMARCReporting-0.2.1/tests/test_FileLister.py → dmarcreporting-0.3.1/tests/test_filelister.py +12 -12
  16. {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/tests/test_parser_dkim.py +40 -2
  17. {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/tests/test_parser_dmarc.py +43 -6
  18. {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/tests/test_parser_spf.py +32 -1
  19. DMARCReporting-0.2.1/tests/test_acceptance.py +0 -37
  20. {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/DMARCReporting/__main__.py +0 -0
  21. {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/DMARCReporting/dns.py +0 -0
  22. {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/DMARCReporting/renderer.py +0 -0
  23. {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/DMARCReporting.egg-info/dependency_links.txt +0 -0
  24. {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/DMARCReporting.egg-info/entry_points.txt +0 -0
  25. {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/DMARCReporting.egg-info/requires.txt +0 -0
  26. {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/DMARCReporting.egg-info/top_level.txt +0 -0
  27. {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/LICENSE +0 -0
  28. {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/README.md +0 -0
  29. {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/setup.cfg +0 -0
  30. {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/tests/test_dns.py +0 -0
@@ -1,4 +1,4 @@
1
- __version__ = "0.1.1"
1
+ __version__ = "0.3.1"
2
2
 
3
3
  import argparse
4
4
  from DMARCReporting.cli import CLI
@@ -1,12 +1,10 @@
1
-
2
- from os.path import join
3
1
  import io
4
2
 
5
3
  from DMARCReporting.decompressor import DecompressorFactory
6
4
  from DMARCReporting.parser import DMARCRuaParser
7
5
  from DMARCReporting.renderer import ConsoleRenderer, CSVRenderer
8
6
  from DMARCReporting.dns import DNS
9
- from DMARCReporting.FileLister import FileLister
7
+ from DMARCReporting.filelister import FileLister
10
8
 
11
9
 
12
10
  class CLI():
@@ -14,14 +12,17 @@ class CLI():
14
12
  files = FileLister().list(input_dir)
15
13
 
16
14
  parser = DMARCRuaParser(DNS())
17
- renderer = ConsoleRenderer()
18
15
  all_data = []
19
16
 
20
17
  for file in files:
21
- file_path = join(input_dir, file)
22
- report = DecompressorFactory.create(file_path).decompress(file_path)
23
- data = parser.parse(io.BytesIO(report), include_all=show_all)
24
- all_data += [[*row, file] for row in data]
18
+ try:
19
+ report = DecompressorFactory.create(file).decompress(file)
20
+ data = parser.parse(io.BytesIO(report), include_all=show_all)
21
+ all_data += [[*row, file] for row in data]
22
+ except Exception as e:
23
+ print(f"Error processing file {file}: {e}")
24
+
25
+ renderer = ConsoleRenderer()
25
26
  renderer.render("All", all_data)
26
27
 
27
28
  if csv_output_file:
@@ -1,4 +1,6 @@
1
1
  headers = [
2
+ "Begin Date",
3
+ "End Date",
2
4
  "Source IP",
3
5
  "Source Host",
4
6
  "Payload From (From:)",
@@ -1,5 +1,6 @@
1
1
  import gzip
2
2
  import zipfile
3
+ import zlib
3
4
 
4
5
 
5
6
  class DecompressorFactory():
@@ -13,8 +14,19 @@ class DecompressorFactory():
13
14
 
14
15
  class GZipDecompressor():
15
16
  def decompress(self, gzip_file_path):
16
- with gzip.open(gzip_file_path, 'rb') as f:
17
- return f.read()
17
+ try:
18
+ with gzip.open(gzip_file_path, 'rb') as f:
19
+ return f.read()
20
+ except gzip.BadGzipFile:
21
+ return self._decompress_with_zlib(gzip_file_path)
22
+
23
+ def _decompress_with_zlib(self, gzip_file_path):
24
+ with open(gzip_file_path, 'rb') as f:
25
+ do = zlib.decompressobj(wbits=31)
26
+ data = do.decompress(f.read())
27
+ remainder = do.unused_data
28
+ f.seek(f.tell() - len(remainder))
29
+ return data
18
30
 
19
31
 
20
32
  class ZipDecompressor():
@@ -1,4 +1,3 @@
1
-
2
1
  from os import listdir
3
2
  from os.path import isfile
4
3
  from os.path import join
@@ -8,7 +7,7 @@ import pathlib
8
7
  class FileLister():
9
8
  def list(self, input_dir):
10
9
  return sorted([
11
- f for f in listdir(input_dir) if self._is_zip_or_gz_file(input_dir, f)
10
+ join(input_dir, f) for f in listdir(input_dir) if self._is_zip_or_gz_file(input_dir, f)
12
11
  ])
13
12
 
14
13
  def _is_zip_or_gz_file(self, input_dir, file_name):
@@ -1,3 +1,5 @@
1
+ from datetime import datetime
2
+
1
3
  from lxml import etree as ET
2
4
 
3
5
  xpath_all = ".//record"
@@ -18,6 +20,9 @@ class DMARCRuaParser:
18
20
 
19
21
  def parse(self, rua_report, include_all=False):
20
22
  root = ET.parse(rua_report)
23
+
24
+ dates = self._parse_dates(root)
25
+
21
26
  records = root.xpath(xpath_all if include_all else xpath_failed)
22
27
  data = []
23
28
  for record in records:
@@ -29,6 +34,8 @@ class DMARCRuaParser:
29
34
 
30
35
  data.append(
31
36
  [
37
+ dates["begin"],
38
+ dates["end"],
32
39
  row["source_ip"],
33
40
  row["source_host"],
34
41
  payload_from,
@@ -43,6 +50,16 @@ class DMARCRuaParser:
43
50
  )
44
51
  return data
45
52
 
53
+ def _parse_dates(self, root):
54
+ result = {}
55
+
56
+ begin = int(root.xpath("/feedback/report_metadata/date_range/begin/text()")[0])
57
+ end = int(root.xpath("/feedback/report_metadata/date_range/end/text()")[0])
58
+
59
+ result["begin"] = datetime.fromtimestamp(begin).isoformat()
60
+ result["end"] = datetime.fromtimestamp(end).isoformat()
61
+ return result
62
+
46
63
  def _parse_row(self, record):
47
64
  result = {}
48
65
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: DMARCReporting
3
- Version: 0.2.1
3
+ Version: 0.3.1
4
4
  Summary: Simple tool to extract error reports from DMARC files
5
5
  Home-page: https://github.com/MozaicWorks/DMARCReporting
6
6
  Author: Mozaic Works
@@ -13,6 +13,17 @@ Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
14
  Requires-Dist: lxml
15
15
  Requires-Dist: tabulate
16
+ Dynamic: author
17
+ Dynamic: author-email
18
+ Dynamic: classifier
19
+ Dynamic: description
20
+ Dynamic: description-content-type
21
+ Dynamic: home-page
22
+ Dynamic: license
23
+ Dynamic: license-file
24
+ Dynamic: requires-dist
25
+ Dynamic: requires-python
26
+ Dynamic: summary
16
27
 
17
28
  # DMARCReporting ![Build Status](https://github.com/mozaicworks/DMARCReporting/actions/workflows/build.yml/badge.svg?event=push)
18
29
 
@@ -2,13 +2,13 @@ LICENSE
2
2
  README.md
3
3
  setup.cfg
4
4
  setup.py
5
- DMARCReporting/FileLister.py
6
5
  DMARCReporting/__init__.py
7
6
  DMARCReporting/__main__.py
8
7
  DMARCReporting/cli.py
9
8
  DMARCReporting/constants.py
10
9
  DMARCReporting/decompressor.py
11
10
  DMARCReporting/dns.py
11
+ DMARCReporting/filelister.py
12
12
  DMARCReporting/parser.py
13
13
  DMARCReporting/renderer.py
14
14
  DMARCReporting.egg-info/PKG-INFO
@@ -17,12 +17,12 @@ DMARCReporting.egg-info/dependency_links.txt
17
17
  DMARCReporting.egg-info/entry_points.txt
18
18
  DMARCReporting.egg-info/requires.txt
19
19
  DMARCReporting.egg-info/top_level.txt
20
- tests/test_FileLister.py
21
20
  tests/test_acceptance.py
22
21
  tests/test_console_renderer.py
23
22
  tests/test_csv_renderer.py
24
23
  tests/test_decompressor.py
25
24
  tests/test_dns.py
25
+ tests/test_filelister.py
26
26
  tests/test_parser_dkim.py
27
27
  tests/test_parser_dmarc.py
28
28
  tests/test_parser_spf.py
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: DMARCReporting
3
- Version: 0.2.1
3
+ Version: 0.3.1
4
4
  Summary: Simple tool to extract error reports from DMARC files
5
5
  Home-page: https://github.com/MozaicWorks/DMARCReporting
6
6
  Author: Mozaic Works
@@ -13,6 +13,17 @@ Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
14
  Requires-Dist: lxml
15
15
  Requires-Dist: tabulate
16
+ Dynamic: author
17
+ Dynamic: author-email
18
+ Dynamic: classifier
19
+ Dynamic: description
20
+ Dynamic: description-content-type
21
+ Dynamic: home-page
22
+ Dynamic: license
23
+ Dynamic: license-file
24
+ Dynamic: requires-dist
25
+ Dynamic: requires-python
26
+ Dynamic: summary
16
27
 
17
28
  # DMARCReporting ![Build Status](https://github.com/mozaicworks/DMARCReporting/actions/workflows/build.yml/badge.svg?event=push)
18
29
 
@@ -1,6 +1,8 @@
1
1
  import pathlib
2
2
  from setuptools import setup, find_packages
3
3
 
4
+ import DMARCReporting
5
+
4
6
  # The directory containing this file
5
7
  HERE = pathlib.Path(__file__).parent
6
8
 
@@ -10,7 +12,7 @@ README = (HERE / "README.md").read_text()
10
12
  # This call to setup() does all the work
11
13
  setup(
12
14
  name="DMARCReporting",
13
- version="0.2.1",
15
+ version=DMARCReporting.__version__,
14
16
  description="Simple tool to extract error reports from DMARC files",
15
17
  long_description=README,
16
18
  long_description_content_type="text/markdown",
@@ -0,0 +1,122 @@
1
+ import os
2
+ import sys
3
+ import io
4
+ import time
5
+ from unittest.mock import patch
6
+
7
+ import pytest
8
+
9
+ from .context import DMARCReporting # noqa F401
10
+ from DMARCReporting.cli import CLI
11
+
12
+ test_tz = 'Europe/Brussels'
13
+ env_tz = ''
14
+
15
+
16
+ @pytest.fixture
17
+ def setup_timezone():
18
+ env_tz = time.tzname[0]
19
+ print()
20
+ print(f"save current timezone: {env_tz}")
21
+ print(f"set timezone for tests to {test_tz}")
22
+ os.environ['TZ'] = test_tz
23
+ time.tzset()
24
+ print(f"timezone is now {time.tzname[0]}")
25
+ yield
26
+ print()
27
+ print(f"reset timezone back to {env_tz}")
28
+ os.environ['TZ'] = env_tz
29
+ time.tzset()
30
+ print(f"timezone is now {time.tzname[0]}")
31
+
32
+
33
+ @patch('socket.gethostbyaddr')
34
+ def test_render(gethostbyaddr_mock, setup_timezone):
35
+ gethostbyaddr_mock.side_effect = [
36
+ ("Unknown host", [], []),
37
+ ("208-90-221-45.static.flhsi.com", [], []),
38
+ ("Unknown host", [], []),
39
+ ("208-90-221-45.static.flhsi.com", [], []),
40
+ ]
41
+ try:
42
+ output = io.StringIO()
43
+ sys.stdout = output
44
+
45
+ cli = CLI()
46
+ cli.execute("./samples")
47
+
48
+ expected = (
49
+ "Begin Date End Date Source IP Source Host Payload From (From:) Envelop From (MAIL FROM) DMARC DKIM Align DKIM Auth SPF Align SPF Auth Count File\n" # noqa E501
50
+ "------------------- ------------------- ------------- ------------------------------ ---------------------- -------------------------- ------- ------------ ----------- ----------- ---------- ------- ------------------------\n" # noqa E501
51
+ "2021-11-25T01:00:00 2021-11-26T00:59:59 80.96.161.193 Unknown host bellous.com bellous.com none pass pass fail fail 3 ./samples/report.xml.gz\n" # noqa E501
52
+ "2021-11-25T01:00:00 2021-11-26T00:59:59 208.90.221.45 208-90-221-45.static.flhsi.com bellous.com calendar.yambo.com none pass pass fail pass 32 ./samples/report.xml.gz\n" # noqa E501
53
+ "2021-11-25T01:00:00 2021-11-26T00:59:59 80.96.161.193 Unknown host disicious.com disicious.com none pass pass fail fail 3 ./samples/report.xml.zip\n" # noqa E501
54
+ "2021-11-25T01:00:00 2021-11-26T00:59:59 208.90.221.45 208-90-221-45.static.flhsi.com disicious.com calendar.trumbee.com none pass pass fail pass 32 ./samples/report.xml.zip\n" # noqa E501
55
+ )
56
+ actual = output.getvalue()
57
+
58
+ assert expected == actual
59
+
60
+ finally:
61
+ sys.stdout = sys.__stdout__
62
+
63
+
64
+ @patch('socket.gethostbyaddr')
65
+ def test_render_all(gethostbyaddr_mock, setup_timezone):
66
+ gethostbyaddr_mock.side_effect = [
67
+ ("smtp.bellous.com", [], []),
68
+ ("smtp.bellous.com", [], []),
69
+ ("smtp.bellous.com", [], []),
70
+ ("smtp.bellous.com", [], []),
71
+ ("smtp.bellous.com", [], []),
72
+ ("Unknown host", [], []),
73
+ ("208-90-221-45.static.flhsi.com", [], []),
74
+ ("smtp.disicious.com", [], []),
75
+ ("smtp.disicious.com", [], []),
76
+ ("smtp.disicious.com", [], []),
77
+ ("smtp.disicious.com", [], []),
78
+ ("smtp.disicious.com", [], []),
79
+ ("Unknown host", [], []),
80
+ ("208-90-221-45.static.flhsi.com", [], []),
81
+ ("smtp.abbove.com", [], []),
82
+ ("smtp.abbove.com", [], []),
83
+ ("smtp.abbove.com", [], []),
84
+ ("smtp.abbove.com", [], []),
85
+ ("smtp.abbove.com", [], []),
86
+ ]
87
+ try:
88
+ output = io.StringIO()
89
+ sys.stdout = output
90
+
91
+ cli = CLI()
92
+ cli.execute("./samples", csv_output_file=None, show_all=True)
93
+
94
+ expected = (
95
+ "Begin Date End Date Source IP Source Host Payload From (From:) Envelop From (MAIL FROM) DMARC DKIM Align DKIM Auth SPF Align SPF Auth Count File\n" # noqa E501
96
+ "------------------- ------------------- -------------- ------------------------------ ---------------------- -------------------------- ------- ------------ ----------- ----------- ---------- ------- ----------------------------------\n" # noqa E501
97
+ "2021-11-25T01:00:00 2021-11-26T00:59:59 207.90.221.45 smtp.bellous.com bellous.com bellous.com none pass pass pass pass 8 ./samples/report.xml.gz\n" # noqa E501
98
+ "2021-11-25T01:00:00 2021-11-26T00:59:59 207.86.220.40 smtp.bellous.com bellous.com bellous.com none pass pass pass pass 22 ./samples/report.xml.gz\n" # noqa E501
99
+ "2021-11-25T01:00:00 2021-11-26T00:59:59 198.3.186.1 smtp.bellous.com bellous.com mail.bellous.com none pass pass pass pass 68 ./samples/report.xml.gz\n" # noqa E501
100
+ "2021-11-25T01:00:00 2021-11-26T00:59:59 210.81.222.70 smtp.bellous.com bellous.com bellous.com none pass pass pass pass 6 ./samples/report.xml.gz\n" # noqa E501
101
+ "2021-11-25T01:00:00 2021-11-26T00:59:59 197.3.177.11 smtp.bellous.com bellous.com mail.bellous.com none pass pass pass pass 81 ./samples/report.xml.gz\n" # noqa E501
102
+ "2021-11-25T01:00:00 2021-11-26T00:59:59 80.96.161.193 Unknown host bellous.com bellous.com none pass pass fail fail 3 ./samples/report.xml.gz\n" # noqa E501
103
+ "2021-11-25T01:00:00 2021-11-26T00:59:59 208.90.221.45 208-90-221-45.static.flhsi.com bellous.com calendar.yambo.com none pass pass fail pass 32 ./samples/report.xml.gz\n" # noqa E501
104
+ "2021-11-25T01:00:00 2021-11-26T00:59:59 207.90.221.45 smtp.disicious.com disicious.com disicious.com none pass pass pass pass 8 ./samples/report.xml.zip\n" # noqa E501
105
+ "2021-11-25T01:00:00 2021-11-26T00:59:59 207.86.220.40 smtp.disicious.com disicious.com disicious.com none pass pass pass pass 22 ./samples/report.xml.zip\n" # noqa E501
106
+ "2021-11-25T01:00:00 2021-11-26T00:59:59 198.3.186.1 smtp.disicious.com disicious.com mail.disicious.com none pass pass pass pass 68 ./samples/report.xml.zip\n" # noqa E501
107
+ "2021-11-25T01:00:00 2021-11-26T00:59:59 210.81.222.70 smtp.disicious.com disicious.com disicious.com none pass pass pass pass 6 ./samples/report.xml.zip\n" # noqa E501
108
+ "2021-11-25T01:00:00 2021-11-26T00:59:59 197.3.177.11 smtp.disicious.com disicious.com mail.disicious.com none pass pass pass pass 81 ./samples/report.xml.zip\n" # noqa E501
109
+ "2021-11-25T01:00:00 2021-11-26T00:59:59 80.96.161.193 Unknown host disicious.com disicious.com none pass pass fail fail 3 ./samples/report.xml.zip\n" # noqa E501
110
+ "2021-11-25T01:00:00 2021-11-26T00:59:59 208.90.221.45 208-90-221-45.static.flhsi.com disicious.com calendar.trumbee.com none pass pass fail pass 32 ./samples/report.xml.zip\n" # noqa E501
111
+ "2023-08-30T02:00:00 2023-08-31T01:59:59 209.85.208.179 smtp.abbove.com abbove.com abbove.com none pass pass pass pass 1 ./samples/trailing_garbage1.xml.gz\n" # noqa E501
112
+ "2023-08-30T02:00:00 2023-08-31T01:59:59 209.85.208.170 smtp.abbove.com abbove.com abbove.com none pass pass pass pass 1 ./samples/trailing_garbage1.xml.gz\n" # noqa E501
113
+ "2023-08-30T02:00:00 2023-08-31T01:59:59 209.85.208.181 smtp.abbove.com abbove.com abbove.com none pass pass pass pass 1 ./samples/trailing_garbage1.xml.gz\n" # noqa E501
114
+ "2023-08-31T02:00:00 2023-09-01T01:59:59 209.85.219.176 smtp.abbove.com abbove.com abbove.com none pass pass pass pass 1 ./samples/trailing_garbage2.xml.gz\n" # noqa E501
115
+ "2023-08-31T02:00:00 2023-09-01T01:59:59 209.85.219.178 smtp.abbove.com abbove.com abbove.com none pass pass pass pass 1 ./samples/trailing_garbage2.xml.gz\n" # noqa E501
116
+ )
117
+ actual = output.getvalue()
118
+
119
+ assert expected == actual
120
+
121
+ finally:
122
+ sys.stdout = sys.__stdout__
@@ -31,6 +31,8 @@ def test_render_with_data():
31
31
 
32
32
  data = [
33
33
  [
34
+ "begin_date",
35
+ "end_date",
34
36
  "source_ip",
35
37
  "source_host",
36
38
  "payload_from",
@@ -49,9 +51,9 @@ def test_render_with_data():
49
51
  sut.render("ruareport_file", data)
50
52
 
51
53
  expected = (
52
- "Source IP Source Host Payload From (From:) Envelop From (MAIL FROM) DMARC DKIM Align DKIM Auth SPF Align SPF Auth Count File\n" # noqa E501
53
- "----------- ------------- ---------------------- -------------------------- ----------------- ------------ ----------- ----------- ---------- ------- --------------\n" # noqa E501
54
- "source_ip source_host payload_from envelop_from dmarc_disposition dkim_aligned dkim_auth spf_aligned spf_auth 42 ruareport_file\n" # noqa E501
54
+ "Begin Date End Date Source IP Source Host Payload From (From:) Envelop From (MAIL FROM) DMARC DKIM Align DKIM Auth SPF Align SPF Auth Count File\n" # noqa E501
55
+ "------------ ---------- ----------- ------------- ---------------------- -------------------------- ----------------- ------------ ----------- ----------- ---------- ------- --------------\n" # noqa E501
56
+ "begin_date end_date source_ip source_host payload_from envelop_from dmarc_disposition dkim_aligned dkim_auth spf_aligned spf_auth 42 ruareport_file\n" # noqa E501
55
57
  )
56
58
  actual = output.getvalue()
57
59
 
@@ -15,7 +15,7 @@ def test_render_without_data():
15
15
  sut = CSVRenderer()
16
16
  sut.render(csv_file, data)
17
17
 
18
- expected = "Source IP,Source Host,Payload From (From:),Envelop From (MAIL FROM),DMARC,DKIM Align,DKIM Auth,SPF Align,SPF Auth,Count,File\n" # noqa E501
18
+ expected = "Begin Date,End Date,Source IP,Source Host,Payload From (From:),Envelop From (MAIL FROM),DMARC,DKIM Align,DKIM Auth,SPF Align,SPF Auth,Count,File\n" # noqa E501
19
19
  actual = open(csv_file, "r").read()
20
20
 
21
21
  assert expected == actual
@@ -31,6 +31,8 @@ def test_render_with_data():
31
31
 
32
32
  data = [
33
33
  [
34
+ "begin_date",
35
+ "end_date",
34
36
  "source_ip",
35
37
  "source_host",
36
38
  "payload_from",
@@ -49,8 +51,8 @@ def test_render_with_data():
49
51
  sut.render(csv_file, data)
50
52
 
51
53
  expected = (
52
- "Source IP,Source Host,Payload From (From:),Envelop From (MAIL FROM),DMARC,DKIM Align,DKIM Auth,SPF Align,SPF Auth,Count,File\n" # noqa E501
53
- "source_ip,source_host,payload_from,envelop_from,dmarc_disposition,dkim_aligned,dkim_auth,spf_aligned,spf_auth,42,ruareport_file\n" # noqa E501
54
+ "Begin Date,End Date,Source IP,Source Host,Payload From (From:),Envelop From (MAIL FROM),DMARC,DKIM Align,DKIM Auth,SPF Align,SPF Auth,Count,File\n" # noqa E501
55
+ "begin_date,end_date,source_ip,source_host,payload_from,envelop_from,dmarc_disposition,dkim_aligned,dkim_auth,spf_aligned,spf_auth,42,ruareport_file\n" # noqa E501
54
56
  )
55
57
  actual = actual = open(csv_file, "r").read()
56
58
 
@@ -30,7 +30,6 @@ def test_factory_raises_not_implemented():
30
30
  def test_decompress_gzip():
31
31
  sut = GZipDecompressor()
32
32
  actual = sut.decompress("./tests/data/sample.txt.gz")
33
-
34
33
  assert "Hello world, gzip!" == actual.decode("utf-8")
35
34
 
36
35
 
@@ -39,3 +38,10 @@ def test_decompress_zip():
39
38
  actual = sut.decompress("./tests/data/sample.txt.zip")
40
39
 
41
40
  assert "Hello world, zip!" == actual.decode("utf-8")
41
+
42
+
43
+ def test_decompress_trailing_garbage_gzip():
44
+ sut = GZipDecompressor()
45
+ actual = sut.decompress("./tests/data/trailing_garbage.xml.gz")
46
+ expected = open("./tests/data/trailing_garbage.xml", "r").read()
47
+ assert expected == actual.decode("utf-8")
@@ -1,9 +1,11 @@
1
-
2
1
  import unittest
3
- from .context import DMARCReporting # noqa F401
4
- from DMARCReporting.FileLister import FileLister
5
2
  import tempfile
6
3
  import os
4
+ from os.path import join
5
+
6
+ from .context import DMARCReporting # noqa F401
7
+ from DMARCReporting.filelister import FileLister
8
+
7
9
  from parameterized import parameterized
8
10
 
9
11
 
@@ -17,19 +19,17 @@ class TestFileLister(unittest.TestCase):
17
19
  ("sort_files", ["2.gz", "z.zip", "1.gz", "a.zip"], ["1.gz", "2.gz", "a.zip", "z.zip"]),
18
20
  ("only_gz_and_zip_files", ["z.zip", "1.gz", "3.txt", "ttt.jpg"], ["1.gz", "z.zip"]),
19
21
  ])
20
- def test_file_lister(self, name, filesList, expected):
22
+ def test_file_lister(self, name, filesList, expectedFilesList):
21
23
  def listerFunction(dirName): return FileLister().list(dirName)
22
24
 
23
- actual = self.list_files_with_function(filesList, listerFunction)
25
+ # Create temporary directory, temporary files, list them with the lister function, assert, and delete them
26
+ with tempfile.TemporaryDirectory() as testDir:
27
+ [self.create_test_file(testDir, fileName) for fileName in filesList]
28
+ actual = listerFunction(testDir)
29
+ expected = [join(testDir, f) for f in expectedFilesList]
24
30
 
25
- self.assertListEqual(expected, actual)
31
+ self.assertListEqual(expected, actual)
26
32
 
27
33
  def create_test_file(self, path, filename):
28
34
  with open(os.path.join(path, filename), 'w'):
29
35
  pass
30
-
31
- def list_files_with_function(self, filesList, listerFunction):
32
- ''' Create temporary directory, temporary files, list them with the lister function, and delete them'''
33
- with tempfile.TemporaryDirectory() as testDir:
34
- [self.create_test_file(testDir, fileName) for fileName in filesList]
35
- return listerFunction(testDir)
@@ -1,16 +1,44 @@
1
+ import os
1
2
  import io
3
+ import time
2
4
 
3
5
  import pytest
4
6
 
5
7
  from .context import DMARCReporting # noqa F401
6
8
  from DMARCReporting.parser import DMARCRuaParser
7
9
 
10
+ test_tz = 'Europe/Brussels'
11
+ env_tz = ''
12
+
13
+
14
+ @pytest.fixture
15
+ def setup_timezone():
16
+ env_tz = time.tzname[0]
17
+ print()
18
+ print(f"save current timezone: {env_tz}")
19
+ print(f"set timezone for tests to {test_tz}")
20
+ os.environ['TZ'] = test_tz
21
+ time.tzset()
22
+ print(f"timezone is now {time.tzname[0]}")
23
+ yield
24
+ print()
25
+ print(f"reset timezone back to {env_tz}")
26
+ os.environ['TZ'] = env_tz
27
+ time.tzset()
28
+ print(f"timezone is now {time.tzname[0]}")
29
+
8
30
 
9
31
  @pytest.fixture
10
32
  def not_authenticated():
11
33
  return io.StringIO(
12
34
  """
13
35
  <feedback>
36
+ <report_metadata>
37
+ <date_range>
38
+ <begin>1703237232</begin>
39
+ <end>1703280196</end>
40
+ </date_range>
41
+ </report_metadata>
14
42
  <record>
15
43
  <row>
16
44
  <source_ip>201.81.220.40</source_ip>
@@ -44,11 +72,13 @@ class DNSStub:
44
72
  return "mail.email.com"
45
73
 
46
74
 
47
- def test_when_not_authenticated(not_authenticated):
75
+ def test_when_not_authenticated(not_authenticated, setup_timezone):
48
76
  sut = DMARCRuaParser(DNSStub())
49
77
  actual = sut.parse(not_authenticated)
50
78
  expected = [
51
79
  [
80
+ "2023-12-22T10:27:12",
81
+ "2023-12-22T22:23:16",
52
82
  "201.81.220.40",
53
83
  "mail.email.com",
54
84
  "email.com",
@@ -69,6 +99,12 @@ def authenticated_two_records():
69
99
  return io.StringIO(
70
100
  """
71
101
  <feedback>
102
+ <report_metadata>
103
+ <date_range>
104
+ <begin>1703237232</begin>
105
+ <end>1703280196</end>
106
+ </date_range>
107
+ </report_metadata>
72
108
  <record>
73
109
  <row>
74
110
  <source_ip>201.81.220.40</source_ip>
@@ -102,11 +138,13 @@ def authenticated_two_records():
102
138
  )
103
139
 
104
140
 
105
- def test_when_not_authenticated_having_two_records(authenticated_two_records):
141
+ def test_when_not_authenticated_having_two_records(authenticated_two_records, setup_timezone):
106
142
  sut = DMARCRuaParser(DNSStub())
107
143
  actual = sut.parse(authenticated_two_records)
108
144
  expected = [
109
145
  [
146
+ "2023-12-22T10:27:12",
147
+ "2023-12-22T22:23:16",
110
148
  "201.81.220.40",
111
149
  "mail.email.com",
112
150
  "email.com",
@@ -1,4 +1,6 @@
1
+ import os
1
2
  import io
3
+ import time
2
4
 
3
5
  import pytest
4
6
 
@@ -6,10 +8,37 @@ from .context import DMARCReporting # noqa F401
6
8
  from DMARCReporting.parser import DMARCRuaParser
7
9
 
8
10
 
11
+ test_tz = 'Europe/Brussels'
12
+ env_tz = ''
13
+
14
+
15
+ @pytest.fixture
16
+ def setup_timezone():
17
+ env_tz = time.tzname[0]
18
+ print()
19
+ print(f"save current timezone: {env_tz}")
20
+ print(f"set timezone for tests to {test_tz}")
21
+ os.environ['TZ'] = test_tz
22
+ time.tzset()
23
+ print(f"timezone is now {time.tzname[0]}")
24
+ yield
25
+ print()
26
+ print(f"reset timezone back to {env_tz}")
27
+ os.environ['TZ'] = env_tz
28
+ time.tzset()
29
+ print(f"timezone is now {time.tzname[0]}")
30
+
31
+
9
32
  def rua_report(disposition="none", spf_aligned="pass", dkim_aligned="pass"):
10
33
  return io.StringIO(
11
34
  """
12
35
  <feedback>
36
+ <report_metadata>
37
+ <date_range>
38
+ <begin>1703237232</begin>
39
+ <end>1703280196</end>
40
+ </date_range>
41
+ </report_metadata>
13
42
  <record>
14
43
  <row>
15
44
  <source_ip>101.0.122.38</source_ip>
@@ -79,11 +108,13 @@ class DNSStub:
79
108
  return "mail.email.com"
80
109
 
81
110
 
82
- def test_when_dmarc_disposition_quarantine(rua_report_quarantine):
111
+ def test_when_dmarc_disposition_quarantine(rua_report_quarantine, setup_timezone):
83
112
  sut = DMARCRuaParser(DNSStub())
84
113
  actual = sut.parse(rua_report_quarantine)
85
114
  expected = [
86
115
  [
116
+ "2023-12-22T10:27:12",
117
+ "2023-12-22T22:23:16",
87
118
  "101.0.122.38",
88
119
  "mail.email.com",
89
120
  "email.com",
@@ -99,17 +130,19 @@ def test_when_dmarc_disposition_quarantine(rua_report_quarantine):
99
130
  assert expected == actual
100
131
 
101
132
 
102
- def test_when_dmarc_disposition_none(rua_report_none):
133
+ def test_when_dmarc_disposition_none(rua_report_none, setup_timezone):
103
134
  sut = DMARCRuaParser(DNSStub())
104
135
  actual = sut.parse(rua_report_none)
105
136
  assert [] == actual
106
137
 
107
138
 
108
- def test_when_dmarc_disposition_reject(rua_report_reject):
139
+ def test_when_dmarc_disposition_reject(rua_report_reject, setup_timezone):
109
140
  sut = DMARCRuaParser(DNSStub())
110
141
  actual = sut.parse(rua_report_reject)
111
142
  expected = [
112
143
  [
144
+ "2023-12-22T10:27:12",
145
+ "2023-12-22T22:23:16",
113
146
  "101.0.122.38",
114
147
  "mail.email.com",
115
148
  "email.com",
@@ -125,17 +158,19 @@ def test_when_dmarc_disposition_reject(rua_report_reject):
125
158
  assert expected == actual
126
159
 
127
160
 
128
- def test_when_spf_and_dkim_aligned(rua_report_spf_and_dkim_aligned):
161
+ def test_when_spf_and_dkim_aligned(rua_report_spf_and_dkim_aligned, setup_timezone):
129
162
  sut = DMARCRuaParser(DNSStub())
130
163
  actual = sut.parse(rua_report_spf_and_dkim_aligned)
131
164
  assert [] == actual
132
165
 
133
166
 
134
- def test_when_spf_not_aligned(rua_report_spf_not_aligned):
167
+ def test_when_spf_not_aligned(rua_report_spf_not_aligned, setup_timezone):
135
168
  sut = DMARCRuaParser(DNSStub())
136
169
  actual = sut.parse(rua_report_spf_not_aligned)
137
170
  expected = [
138
171
  [
172
+ "2023-12-22T10:27:12",
173
+ "2023-12-22T22:23:16",
139
174
  "101.0.122.38",
140
175
  "mail.email.com",
141
176
  "email.com",
@@ -151,11 +186,13 @@ def test_when_spf_not_aligned(rua_report_spf_not_aligned):
151
186
  assert expected == actual
152
187
 
153
188
 
154
- def test_when_dkim_not_aligned(rua_report_dkim_not_aligned):
189
+ def test_when_dkim_not_aligned(rua_report_dkim_not_aligned, setup_timezone):
155
190
  sut = DMARCRuaParser(DNSStub())
156
191
  actual = sut.parse(rua_report_dkim_not_aligned)
157
192
  expected = [
158
193
  [
194
+ "2023-12-22T10:27:12",
195
+ "2023-12-22T22:23:16",
159
196
  "101.0.122.38",
160
197
  "mail.email.com",
161
198
  "email.com",
@@ -1,4 +1,6 @@
1
+ import os
1
2
  import io
3
+ import time
2
4
 
3
5
  import pytest
4
6
 
@@ -6,11 +8,38 @@ from .context import DMARCReporting # noqa F401
6
8
  from DMARCReporting.parser import DMARCRuaParser
7
9
 
8
10
 
11
+ test_tz = 'Europe/Brussels'
12
+ env_tz = ''
13
+
14
+
15
+ @pytest.fixture
16
+ def setup_timezone():
17
+ env_tz = time.tzname[0]
18
+ print()
19
+ print(f"save current timezone: {env_tz}")
20
+ print(f"set timezone for tests to {test_tz}")
21
+ os.environ['TZ'] = test_tz
22
+ time.tzset()
23
+ print(f"timezone is now {time.tzname[0]}")
24
+ yield
25
+ print()
26
+ print(f"reset timezone back to {env_tz}")
27
+ os.environ['TZ'] = env_tz
28
+ time.tzset()
29
+ print(f"timezone is now {time.tzname[0]}")
30
+
31
+
9
32
  @pytest.fixture
10
33
  def not_authenticated():
11
34
  return io.StringIO(
12
35
  """
13
36
  <feedback>
37
+ <report_metadata>
38
+ <date_range>
39
+ <begin>1703237232</begin>
40
+ <end>1703280196</end>
41
+ </date_range>
42
+ </report_metadata>
14
43
  <record>
15
44
  <row>
16
45
  <source_ip>201.81.220.40</source_ip>
@@ -43,11 +72,13 @@ class DNSStub:
43
72
  return "mail.email.com"
44
73
 
45
74
 
46
- def test_when_spf_not_authenticated(not_authenticated):
75
+ def test_when_spf_not_authenticated(not_authenticated, setup_timezone):
47
76
  sut = DMARCRuaParser(DNSStub())
48
77
  actual = sut.parse(not_authenticated)
49
78
  expected = [
50
79
  [
80
+ "2023-12-22T10:27:12",
81
+ "2023-12-22T22:23:16",
51
82
  "201.81.220.40",
52
83
  "mail.email.com",
53
84
  "email.com",
@@ -1,37 +0,0 @@
1
- import io
2
- import sys
3
- from unittest.mock import patch
4
-
5
- from .context import DMARCReporting # noqa F401
6
- from DMARCReporting.cli import CLI
7
-
8
-
9
- @patch('socket.gethostbyaddr')
10
- def test_render(gethostbyaddr_mock):
11
- gethostbyaddr_mock.side_effect = [
12
- ("Unknown host", [], []),
13
- ("208-90-221-45.static.flhsi.com", [], []),
14
- ("Unknown host", [], []),
15
- ("208-90-221-45.static.flhsi.com", [], []),
16
- ]
17
- try:
18
- output = io.StringIO()
19
- sys.stdout = output
20
-
21
- cli = CLI()
22
- cli.execute("./samples")
23
-
24
- expected = (
25
- "Source IP Source Host Payload From (From:) Envelop From (MAIL FROM) DMARC DKIM Align DKIM Auth SPF Align SPF Auth Count File\n" # noqa E501
26
- "------------- ------------------------------ ---------------------- -------------------------- ------- ------------ ----------- ----------- ---------- ------- --------------\n" # noqa E501
27
- "80.96.161.193 Unknown host bellous.com bellous.com none pass pass fail fail 3 report.xml.gz\n" # noqa E501
28
- "208.90.221.45 208-90-221-45.static.flhsi.com bellous.com calendar.yambo.com none pass pass fail pass 32 report.xml.gz\n" # noqa E501
29
- "80.96.161.193 Unknown host disicious.com disicious.com none pass pass fail fail 3 report.xml.zip\n" # noqa E501
30
- "208.90.221.45 208-90-221-45.static.flhsi.com disicious.com calendar.trumbee.com none pass pass fail pass 32 report.xml.zip\n" # noqa E501
31
- )
32
- actual = output.getvalue()
33
-
34
- assert expected == actual
35
-
36
- finally:
37
- sys.stdout = sys.__stdout__
File without changes
File without changes
File without changes