Update docs

This commit is contained in:
Sean Whalen
2026-07-20 19:53:46 -04:00
parent 759ea2b815
commit d3dee042cc
28 changed files with 751 additions and 158 deletions
+2 -2
View File
@@ -5,14 +5,14 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Overview: module code &mdash; parsedmarc 10.2.2 documentation</title>
<title>Overview: module code &mdash; parsedmarc 10.2.4 documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css?v=b86133f3" />
<link rel="stylesheet" type="text/css" href="../_static/css/theme.css?v=9edc463e" />
<script src="../_static/jquery.js?v=5d32c60e"></script>
<script src="../_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
<script src="../_static/documentation_options.js?v=43731225"></script>
<script src="../_static/documentation_options.js?v=2c8206f1"></script>
<script src="../_static/doctools.js?v=fd6eb6e6"></script>
<script src="../_static/sphinx_highlight.js?v=6ffebe34"></script>
<script src="../_static/js/theme.js"></script>
+113 -16
View File
@@ -5,14 +5,14 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>parsedmarc &mdash; parsedmarc 10.2.2 documentation</title>
<title>parsedmarc &mdash; parsedmarc 10.2.4 documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css?v=b86133f3" />
<link rel="stylesheet" type="text/css" href="../_static/css/theme.css?v=9edc463e" />
<script src="../_static/jquery.js?v=5d32c60e"></script>
<script src="../_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
<script src="../_static/documentation_options.js?v=43731225"></script>
<script src="../_static/documentation_options.js?v=2c8206f1"></script>
<script src="../_static/doctools.js?v=fd6eb6e6"></script>
<script src="../_static/sphinx_highlight.js?v=6ffebe34"></script>
<script src="../_static/js/theme.js"></script>
@@ -1668,6 +1668,27 @@
<span class="n">f</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">parsed_report</span><span class="p">[</span><span class="s2">&quot;auth_failure&quot;</span><span class="p">]</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&quot;,&quot;</span><span class="p">)</span> <span class="k">if</span> <span class="n">f</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
<span class="p">]</span>
<span class="c1"># Feedback-Type is REQUIRED per RFC 5965 §3.1, but some gateways</span>
<span class="c1"># (e.g. Exim/cPanel-based ones that send a plain-text summary without</span>
<span class="c1"># a machine-readable message/feedback-report part) omit it. The</span>
<span class="c1"># Elasticsearch/OpenSearch outputs require the key, so default it</span>
<span class="c1"># instead of dropping the report there (see issue #332).</span>
<span class="k">if</span> <span class="s2">&quot;feedback_type&quot;</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">parsed_report</span><span class="p">:</span>
<span class="n">logger</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span>
<span class="s2">&quot;Failure report missing required &#39;Feedback-Type&#39; field &quot;</span>
<span class="s2">&quot;(RFC 5965 §3.1); defaulting to &#39;auth-failure&#39;&quot;</span>
<span class="p">)</span>
<span class="n">parsed_report</span><span class="p">[</span><span class="s2">&quot;feedback_type&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;auth-failure&quot;</span>
<span class="c1"># Authentication-Results is likewise REQUIRED per RFC 6591 §3.1 and</span>
<span class="c1"># required by the Elasticsearch/OpenSearch outputs.</span>
<span class="k">if</span> <span class="s2">&quot;authentication_results&quot;</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">parsed_report</span><span class="p">:</span>
<span class="n">logger</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span>
<span class="s2">&quot;Failure report missing required &#39;Authentication-Results&#39; &quot;</span>
<span class="s2">&quot;field (RFC 6591 §3.1); defaulting to None&quot;</span>
<span class="p">)</span>
<span class="n">parsed_report</span><span class="p">[</span><span class="s2">&quot;authentication_results&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">optional_fields</span> <span class="o">=</span> <span class="p">[</span>
<span class="s2">&quot;original_envelope_id&quot;</span><span class="p">,</span>
<span class="s2">&quot;dkim_domain&quot;</span><span class="p">,</span>
@@ -2963,6 +2984,37 @@
<span class="k">def</span><span class="w"> </span><span class="nf">_build_report_email_content</span><span class="p">(</span>
<span class="n">results</span><span class="p">:</span> <span class="n">ParsingResults</span><span class="p">,</span>
<span class="o">*</span><span class="p">,</span>
<span class="n">subject</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
<span class="n">attachment_filename</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
<span class="n">message</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">,</span> <span class="nb">list</span><span class="p">[</span><span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">bytes</span><span class="p">]]]:</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;Builds the subject, plain-text body, and zip attachment shared by</span>
<span class="sd"> every report-summary email transport.</span>
<span class="sd"> Returns:</span>
<span class="sd"> A ``(subject, plain_message, attachments)`` tuple.</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">date_string</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s2">&quot;%Y-%m-</span><span class="si">%d</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="k">if</span> <span class="n">attachment_filename</span><span class="p">:</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">attachment_filename</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span><span class="o">.</span><span class="n">endswith</span><span class="p">(</span><span class="s2">&quot;.zip&quot;</span><span class="p">):</span>
<span class="n">attachment_filename</span> <span class="o">+=</span> <span class="s2">&quot;.zip&quot;</span>
<span class="n">filename</span> <span class="o">=</span> <span class="n">attachment_filename</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">filename</span> <span class="o">=</span> <span class="s2">&quot;DMARC-</span><span class="si">{0}</span><span class="s2">.zip&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">date_string</span><span class="p">)</span>
<span class="k">if</span> <span class="n">subject</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">subject</span> <span class="o">=</span> <span class="s2">&quot;DMARC results for </span><span class="si">{0}</span><span class="s2">&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">date_string</span><span class="p">)</span>
<span class="k">if</span> <span class="n">message</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">message</span> <span class="o">=</span> <span class="s2">&quot;DMARC results for </span><span class="si">{0}</span><span class="s2">&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">date_string</span><span class="p">)</span>
<span class="n">zip_bytes</span> <span class="o">=</span> <span class="n">get_report_zip</span><span class="p">(</span><span class="n">results</span><span class="p">)</span>
<span class="n">attachments</span> <span class="o">=</span> <span class="p">[(</span><span class="n">filename</span><span class="p">,</span> <span class="n">zip_bytes</span><span class="p">)]</span>
<span class="k">return</span> <span class="n">subject</span><span class="p">,</span> <span class="n">message</span><span class="p">,</span> <span class="n">attachments</span>
<div class="viewcode-block" id="email_results">
<a class="viewcode-back" href="../api.html#parsedmarc.email_results">[docs]</a>
<span class="k">def</span><span class="w"> </span><span class="nf">email_results</span><span class="p">(</span>
@@ -3002,22 +3054,14 @@
<span class="sd"> message (str): Override the default plain text body</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Emailing report&quot;</span><span class="p">)</span>
<span class="n">date_string</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s2">&quot;%Y-%m-</span><span class="si">%d</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="k">if</span> <span class="n">attachment_filename</span><span class="p">:</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">attachment_filename</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span><span class="o">.</span><span class="n">endswith</span><span class="p">(</span><span class="s2">&quot;.zip&quot;</span><span class="p">):</span>
<span class="n">attachment_filename</span> <span class="o">+=</span> <span class="s2">&quot;.zip&quot;</span>
<span class="n">filename</span> <span class="o">=</span> <span class="n">attachment_filename</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">filename</span> <span class="o">=</span> <span class="s2">&quot;DMARC-</span><span class="si">{0}</span><span class="s2">.zip&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">date_string</span><span class="p">)</span>
<span class="k">assert</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">mail_to</span><span class="p">,</span> <span class="nb">list</span><span class="p">)</span>
<span class="k">if</span> <span class="n">subject</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">subject</span> <span class="o">=</span> <span class="s2">&quot;DMARC results for </span><span class="si">{0}</span><span class="s2">&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">date_string</span><span class="p">)</span>
<span class="k">if</span> <span class="n">message</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">message</span> <span class="o">=</span> <span class="s2">&quot;DMARC results for </span><span class="si">{0}</span><span class="s2">&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">date_string</span><span class="p">)</span>
<span class="n">zip_bytes</span> <span class="o">=</span> <span class="n">get_report_zip</span><span class="p">(</span><span class="n">results</span><span class="p">)</span>
<span class="n">attachments</span> <span class="o">=</span> <span class="p">[(</span><span class="n">filename</span><span class="p">,</span> <span class="n">zip_bytes</span><span class="p">)]</span>
<span class="n">subject</span><span class="p">,</span> <span class="n">message</span><span class="p">,</span> <span class="n">attachments</span> <span class="o">=</span> <span class="n">_build_report_email_content</span><span class="p">(</span>
<span class="n">results</span><span class="p">,</span>
<span class="n">subject</span><span class="o">=</span><span class="n">subject</span><span class="p">,</span>
<span class="n">attachment_filename</span><span class="o">=</span><span class="n">attachment_filename</span><span class="p">,</span>
<span class="n">message</span><span class="o">=</span><span class="n">message</span><span class="p">,</span>
<span class="p">)</span>
<span class="n">send_email</span><span class="p">(</span>
<span class="n">host</span><span class="p">,</span>
@@ -3037,6 +3081,59 @@
<div class="viewcode-block" id="email_results_via_msgraph">
<a class="viewcode-back" href="../api.html#parsedmarc.email_results_via_msgraph">[docs]</a>
<span class="k">def</span><span class="w"> </span><span class="nf">email_results_via_msgraph</span><span class="p">(</span>
<span class="n">results</span><span class="p">:</span> <span class="n">ParsingResults</span><span class="p">,</span>
<span class="n">connection</span><span class="p">:</span> <span class="n">MSGraphConnection</span><span class="p">,</span>
<span class="n">mail_to</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
<span class="o">*</span><span class="p">,</span>
<span class="n">mail_cc</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
<span class="n">mail_bcc</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
<span class="n">subject</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
<span class="n">attachment_filename</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
<span class="n">message</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> Emails parsing results as a zip file via an already-authenticated</span>
<span class="sd"> Microsoft Graph mailbox connection (``/users/{mailbox}/sendMail``),</span>
<span class="sd"> saving a copy to Sent Items.</span>
<span class="sd"> Args:</span>
<span class="sd"> results (dict): Parsing results</span>
<span class="sd"> connection (MSGraphConnection): An already-authenticated Microsoft</span>
<span class="sd"> Graph mailbox connection</span>
<span class="sd"> mail_to (list): A list of addresses to mail to</span>
<span class="sd"> mail_cc (list): A list of addresses to CC</span>
<span class="sd"> mail_bcc (list): A list addresses to BCC</span>
<span class="sd"> subject (str): Overrides the default message subject</span>
<span class="sd"> attachment_filename (str): Override the default attachment filename</span>
<span class="sd"> message (str): Override the default plain text body</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Emailing report via Microsoft Graph&quot;</span><span class="p">)</span>
<span class="n">subject</span><span class="p">,</span> <span class="n">message</span><span class="p">,</span> <span class="n">attachments</span> <span class="o">=</span> <span class="n">_build_report_email_content</span><span class="p">(</span>
<span class="n">results</span><span class="p">,</span>
<span class="n">subject</span><span class="o">=</span><span class="n">subject</span><span class="p">,</span>
<span class="n">attachment_filename</span><span class="o">=</span><span class="n">attachment_filename</span><span class="p">,</span>
<span class="n">message</span><span class="o">=</span><span class="n">message</span><span class="p">,</span>
<span class="p">)</span>
<span class="c1"># Graph derives the From header from the authenticated mailbox and</span>
<span class="c1"># ignores message_from; it&#39;s still passed for API parity with</span>
<span class="c1"># send_message()&#39;s signature.</span>
<span class="n">connection</span><span class="o">.</span><span class="n">send_message</span><span class="p">(</span>
<span class="n">message_from</span><span class="o">=</span><span class="n">connection</span><span class="o">.</span><span class="n">mailbox_name</span> <span class="ow">or</span> <span class="s2">&quot;&quot;</span><span class="p">,</span>
<span class="n">message_to</span><span class="o">=</span><span class="n">mail_to</span><span class="p">,</span>
<span class="n">message_cc</span><span class="o">=</span><span class="n">mail_cc</span><span class="p">,</span>
<span class="n">message_bcc</span><span class="o">=</span><span class="n">mail_bcc</span><span class="p">,</span>
<span class="n">subject</span><span class="o">=</span><span class="n">subject</span><span class="p">,</span>
<span class="n">attachments</span><span class="o">=</span><span class="n">attachments</span><span class="p">,</span>
<span class="n">plain_message</span><span class="o">=</span><span class="n">message</span><span class="p">,</span>
<span class="p">)</span></div>
<span class="c1"># Backward-compatible aliases</span>
<span class="n">parse_forensic_report</span> <span class="o">=</span> <span class="n">parse_failure_report</span>
<span class="n">parsed_forensic_reports_to_csv_rows</span> <span class="o">=</span> <span class="n">parsed_failure_reports_to_csv_rows</span>
+119 -57
View File
@@ -5,14 +5,14 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>parsedmarc.elastic &mdash; parsedmarc 10.2.2 documentation</title>
<title>parsedmarc.elastic &mdash; parsedmarc 10.2.4 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css?v=b86133f3" />
<link rel="stylesheet" type="text/css" href="../../_static/css/theme.css?v=9edc463e" />
<script src="../../_static/jquery.js?v=5d32c60e"></script>
<script src="../../_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
<script src="../../_static/documentation_options.js?v=43731225"></script>
<script src="../../_static/documentation_options.js?v=2c8206f1"></script>
<script src="../../_static/doctools.js?v=fd6eb6e6"></script>
<script src="../../_static/sphinx_highlight.js?v=6ffebe34"></script>
<script src="../../_static/js/theme.js"></script>
@@ -84,10 +84,9 @@
<span class="kn">from</span><span class="w"> </span><span class="nn">__future__</span><span class="w"> </span><span class="kn">import</span> <span class="n">annotations</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">typing</span><span class="w"> </span><span class="kn">import</span> <span class="n">Any</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">typing</span><span class="w"> </span><span class="kn">import</span> <span class="n">TYPE_CHECKING</span><span class="p">,</span> <span class="n">Any</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">elasticsearch.helpers</span><span class="w"> </span><span class="kn">import</span> <span class="n">reindex</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">elasticsearch_dsl</span><span class="w"> </span><span class="kn">import</span> <span class="p">(</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">elasticsearch.dsl</span><span class="w"> </span><span class="kn">import</span> <span class="p">(</span>
<span class="n">Boolean</span><span class="p">,</span>
<span class="n">Date</span><span class="p">,</span>
<span class="n">Document</span><span class="p">,</span>
@@ -98,11 +97,11 @@
<span class="n">Keyword</span><span class="p">,</span>
<span class="n">Nested</span><span class="p">,</span>
<span class="n">Object</span><span class="p">,</span>
<span class="n">Q</span><span class="p">,</span>
<span class="n">Search</span><span class="p">,</span>
<span class="n">Text</span><span class="p">,</span>
<span class="n">connections</span><span class="p">,</span>
<span class="p">)</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">elasticsearch_dsl.search</span><span class="w"> </span><span class="kn">import</span> <span class="n">Q</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">parsedmarc</span><span class="w"> </span><span class="kn">import</span> <span class="n">InvalidFailureReport</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">parsedmarc.log</span><span class="w"> </span><span class="kn">import</span> <span class="n">logger</span>
@@ -129,11 +128,28 @@
<span class="k">class</span><span class="w"> </span><span class="nc">_PolicyOverride</span><span class="p">(</span><span class="n">InnerDoc</span><span class="p">):</span>
<span class="c1"># The elasticsearch.dsl 8.x type stubs use dataclass_transform and only</span>
<span class="c1"># surface dataclass-style annotated fields (``name: M[...] =</span>
<span class="c1"># mapped_field(...)``) as constructor parameters. This module declares</span>
<span class="c1"># fields the pre-8.x way (``name = Text()``), which the runtime fully</span>
<span class="c1"># supports via ObjectBase.__init__(**kwargs), so this TYPE_CHECKING-only</span>
<span class="c1"># declaration restores the real runtime signature for the type checker.</span>
<span class="k">if</span> <span class="n">TYPE_CHECKING</span><span class="p">:</span>
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">pass</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">Text</span><span class="p">()</span>
<span class="n">comment</span> <span class="o">=</span> <span class="n">Text</span><span class="p">()</span>
<span class="k">class</span><span class="w"> </span><span class="nc">_PublishedPolicy</span><span class="p">(</span><span class="n">InnerDoc</span><span class="p">):</span>
<span class="c1"># TYPE_CHECKING __init__: see _PolicyOverride</span>
<span class="k">if</span> <span class="n">TYPE_CHECKING</span><span class="p">:</span>
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">pass</span>
<span class="n">domain</span> <span class="o">=</span> <span class="n">Text</span><span class="p">()</span>
<span class="n">adkim</span> <span class="o">=</span> <span class="n">Text</span><span class="p">()</span>
<span class="n">aspf</span> <span class="o">=</span> <span class="n">Text</span><span class="p">()</span>
@@ -147,6 +163,12 @@
<span class="k">class</span><span class="w"> </span><span class="nc">_DKIMResult</span><span class="p">(</span><span class="n">InnerDoc</span><span class="p">):</span>
<span class="c1"># TYPE_CHECKING __init__: see _PolicyOverride</span>
<span class="k">if</span> <span class="n">TYPE_CHECKING</span><span class="p">:</span>
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">pass</span>
<span class="n">domain</span> <span class="o">=</span> <span class="n">Text</span><span class="p">()</span>
<span class="n">selector</span> <span class="o">=</span> <span class="n">Text</span><span class="p">()</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">Text</span><span class="p">()</span>
@@ -154,6 +176,12 @@
<span class="k">class</span><span class="w"> </span><span class="nc">_SPFResult</span><span class="p">(</span><span class="n">InnerDoc</span><span class="p">):</span>
<span class="c1"># TYPE_CHECKING __init__: see _PolicyOverride</span>
<span class="k">if</span> <span class="n">TYPE_CHECKING</span><span class="p">:</span>
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">pass</span>
<span class="n">domain</span> <span class="o">=</span> <span class="n">Text</span><span class="p">()</span>
<span class="n">scope</span> <span class="o">=</span> <span class="n">Text</span><span class="p">()</span>
<span class="n">results</span> <span class="o">=</span> <span class="n">Text</span><span class="p">()</span>
@@ -161,6 +189,12 @@
<span class="k">class</span><span class="w"> </span><span class="nc">_AggregateReportDoc</span><span class="p">(</span><span class="n">Document</span><span class="p">):</span>
<span class="c1"># TYPE_CHECKING __init__: see _PolicyOverride</span>
<span class="k">if</span> <span class="n">TYPE_CHECKING</span><span class="p">:</span>
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">pass</span>
<span class="k">class</span><span class="w"> </span><span class="nc">Index</span><span class="p">:</span>
<span class="n">name</span> <span class="o">=</span> <span class="s2">&quot;dmarc_aggregate&quot;</span>
@@ -203,7 +237,7 @@
<span class="n">generator</span> <span class="o">=</span> <span class="n">Text</span><span class="p">()</span>
<span class="k">def</span><span class="w"> </span><span class="nf">add_policy_override</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">type_</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">comment</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">policy_overrides</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">_PolicyOverride</span><span class="p">(</span><span class="nb">type</span><span class="o">=</span><span class="n">type_</span><span class="p">,</span> <span class="n">comment</span><span class="o">=</span><span class="n">comment</span><span class="p">))</span> <span class="c1"># pyright: ignore[reportCallIssue]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">policy_overrides</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">_PolicyOverride</span><span class="p">(</span><span class="nb">type</span><span class="o">=</span><span class="n">type_</span><span class="p">,</span> <span class="n">comment</span><span class="o">=</span><span class="n">comment</span><span class="p">))</span>
<span class="k">def</span><span class="w"> </span><span class="nf">add_dkim_result</span><span class="p">(</span>
<span class="bp">self</span><span class="p">,</span>
@@ -219,7 +253,7 @@
<span class="n">result</span><span class="o">=</span><span class="n">result</span><span class="p">,</span>
<span class="n">human_result</span><span class="o">=</span><span class="n">human_result</span><span class="p">,</span>
<span class="p">)</span>
<span class="p">)</span> <span class="c1"># pyright: ignore[reportCallIssue]</span>
<span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">add_spf_result</span><span class="p">(</span>
<span class="bp">self</span><span class="p">,</span>
@@ -235,7 +269,7 @@
<span class="n">result</span><span class="o">=</span><span class="n">result</span><span class="p">,</span>
<span class="n">human_result</span><span class="o">=</span><span class="n">human_result</span><span class="p">,</span>
<span class="p">)</span>
<span class="p">)</span> <span class="c1"># pyright: ignore[reportCallIssue]</span>
<span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">save</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span> <span class="c1"># pyright: ignore[reportIncompatibleMethodOverride]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">passed_dmarc</span> <span class="o">=</span> <span class="kc">False</span>
@@ -245,17 +279,35 @@
<span class="k">class</span><span class="w"> </span><span class="nc">_EmailAddressDoc</span><span class="p">(</span><span class="n">InnerDoc</span><span class="p">):</span>
<span class="c1"># TYPE_CHECKING __init__: see _PolicyOverride</span>
<span class="k">if</span> <span class="n">TYPE_CHECKING</span><span class="p">:</span>
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">pass</span>
<span class="n">display_name</span> <span class="o">=</span> <span class="n">Text</span><span class="p">()</span>
<span class="n">address</span> <span class="o">=</span> <span class="n">Text</span><span class="p">()</span>
<span class="k">class</span><span class="w"> </span><span class="nc">_EmailAttachmentDoc</span><span class="p">(</span><span class="n">Document</span><span class="p">):</span>
<span class="c1"># TYPE_CHECKING __init__: see _PolicyOverride</span>
<span class="k">if</span> <span class="n">TYPE_CHECKING</span><span class="p">:</span>
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">pass</span>
<span class="n">filename</span> <span class="o">=</span> <span class="n">Text</span><span class="p">()</span>
<span class="n">content_type</span> <span class="o">=</span> <span class="n">Text</span><span class="p">()</span>
<span class="n">sha256</span> <span class="o">=</span> <span class="n">Text</span><span class="p">()</span>
<span class="k">class</span><span class="w"> </span><span class="nc">_FailureSampleDoc</span><span class="p">(</span><span class="n">InnerDoc</span><span class="p">):</span>
<span class="c1"># TYPE_CHECKING __init__: see _PolicyOverride</span>
<span class="k">if</span> <span class="n">TYPE_CHECKING</span><span class="p">:</span>
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">pass</span>
<span class="n">raw</span> <span class="o">=</span> <span class="n">Text</span><span class="p">()</span>
<span class="n">headers</span> <span class="o">=</span> <span class="n">Object</span><span class="p">()</span>
<span class="n">headers_only</span> <span class="o">=</span> <span class="n">Boolean</span><span class="p">()</span>
@@ -271,28 +323,34 @@
<span class="n">attachments</span> <span class="o">=</span> <span class="n">Nested</span><span class="p">(</span><span class="n">_EmailAttachmentDoc</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">add_to</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">display_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">address</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">to</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">_EmailAddressDoc</span><span class="p">(</span><span class="n">display_name</span><span class="o">=</span><span class="n">display_name</span><span class="p">,</span> <span class="n">address</span><span class="o">=</span><span class="n">address</span><span class="p">))</span> <span class="c1"># pyright: ignore[reportCallIssue]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">to</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">_EmailAddressDoc</span><span class="p">(</span><span class="n">display_name</span><span class="o">=</span><span class="n">display_name</span><span class="p">,</span> <span class="n">address</span><span class="o">=</span><span class="n">address</span><span class="p">))</span>
<span class="k">def</span><span class="w"> </span><span class="nf">add_reply_to</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">display_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">address</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">reply_to</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
<span class="n">_EmailAddressDoc</span><span class="p">(</span><span class="n">display_name</span><span class="o">=</span><span class="n">display_name</span><span class="p">,</span> <span class="n">address</span><span class="o">=</span><span class="n">address</span><span class="p">)</span>
<span class="p">)</span> <span class="c1"># pyright: ignore[reportCallIssue]</span>
<span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">add_cc</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">display_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">address</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">cc</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">_EmailAddressDoc</span><span class="p">(</span><span class="n">display_name</span><span class="o">=</span><span class="n">display_name</span><span class="p">,</span> <span class="n">address</span><span class="o">=</span><span class="n">address</span><span class="p">))</span> <span class="c1"># pyright: ignore[reportCallIssue]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">cc</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">_EmailAddressDoc</span><span class="p">(</span><span class="n">display_name</span><span class="o">=</span><span class="n">display_name</span><span class="p">,</span> <span class="n">address</span><span class="o">=</span><span class="n">address</span><span class="p">))</span>
<span class="k">def</span><span class="w"> </span><span class="nf">add_bcc</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">display_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">address</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">bcc</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">_EmailAddressDoc</span><span class="p">(</span><span class="n">display_name</span><span class="o">=</span><span class="n">display_name</span><span class="p">,</span> <span class="n">address</span><span class="o">=</span><span class="n">address</span><span class="p">))</span> <span class="c1"># pyright: ignore[reportCallIssue]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">bcc</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">_EmailAddressDoc</span><span class="p">(</span><span class="n">display_name</span><span class="o">=</span><span class="n">display_name</span><span class="p">,</span> <span class="n">address</span><span class="o">=</span><span class="n">address</span><span class="p">))</span>
<span class="k">def</span><span class="w"> </span><span class="nf">add_attachment</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">filename</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">content_type</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">sha256</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">attachments</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
<span class="n">_EmailAttachmentDoc</span><span class="p">(</span>
<span class="n">filename</span><span class="o">=</span><span class="n">filename</span><span class="p">,</span> <span class="n">content_type</span><span class="o">=</span><span class="n">content_type</span><span class="p">,</span> <span class="n">sha256</span><span class="o">=</span><span class="n">sha256</span>
<span class="p">)</span>
<span class="p">)</span> <span class="c1"># pyright: ignore[reportCallIssue]</span>
<span class="p">)</span>
<span class="k">class</span><span class="w"> </span><span class="nc">_FailureReportDoc</span><span class="p">(</span><span class="n">Document</span><span class="p">):</span>
<span class="c1"># TYPE_CHECKING __init__: see _PolicyOverride</span>
<span class="k">if</span> <span class="n">TYPE_CHECKING</span><span class="p">:</span>
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">pass</span>
<span class="k">class</span><span class="w"> </span><span class="nc">Index</span><span class="p">:</span>
<span class="n">name</span> <span class="o">=</span> <span class="s2">&quot;dmarc_failure&quot;</span>
@@ -319,6 +377,12 @@
<span class="k">class</span><span class="w"> </span><span class="nc">_SMTPTLSFailureDetailsDoc</span><span class="p">(</span><span class="n">InnerDoc</span><span class="p">):</span>
<span class="c1"># TYPE_CHECKING __init__: see _PolicyOverride</span>
<span class="k">if</span> <span class="n">TYPE_CHECKING</span><span class="p">:</span>
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">pass</span>
<span class="n">result_type</span> <span class="o">=</span> <span class="n">Text</span><span class="p">()</span>
<span class="n">sending_mta_ip</span> <span class="o">=</span> <span class="n">Ip</span><span class="p">()</span>
<span class="n">receiving_mx_helo</span> <span class="o">=</span> <span class="n">Text</span><span class="p">()</span>
@@ -329,6 +393,12 @@
<span class="k">class</span><span class="w"> </span><span class="nc">_SMTPTLSPolicyDoc</span><span class="p">(</span><span class="n">InnerDoc</span><span class="p">):</span>
<span class="c1"># TYPE_CHECKING __init__: see _PolicyOverride</span>
<span class="k">if</span> <span class="n">TYPE_CHECKING</span><span class="p">:</span>
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">pass</span>
<span class="n">policy_domain</span> <span class="o">=</span> <span class="n">Text</span><span class="p">()</span>
<span class="n">policy_type</span> <span class="o">=</span> <span class="n">Text</span><span class="p">()</span>
<span class="n">policy_strings</span> <span class="o">=</span> <span class="n">Text</span><span class="p">()</span>
@@ -360,10 +430,16 @@
<span class="n">additional_information</span><span class="o">=</span><span class="n">additional_information_uri</span><span class="p">,</span>
<span class="n">failure_reason_code</span><span class="o">=</span><span class="n">failure_reason_code</span><span class="p">,</span>
<span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">failure_details</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">_details</span><span class="p">)</span> <span class="c1"># pyright: ignore[reportCallIssue]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">failure_details</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">_details</span><span class="p">)</span>
<span class="k">class</span><span class="w"> </span><span class="nc">_SMTPTLSReportDoc</span><span class="p">(</span><span class="n">Document</span><span class="p">):</span>
<span class="c1"># TYPE_CHECKING __init__: see _PolicyOverride</span>
<span class="k">if</span> <span class="n">TYPE_CHECKING</span><span class="p">:</span>
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">pass</span>
<span class="k">class</span><span class="w"> </span><span class="nc">Index</span><span class="p">:</span>
<span class="n">name</span> <span class="o">=</span> <span class="s2">&quot;smtp_tls&quot;</span>
@@ -402,7 +478,9 @@
<span class="sd"> Args:</span>
<span class="sd"> hosts (str | list[str]): A single hostname or URL, or list of hostnames or URLs</span>
<span class="sd"> use_ssl (bool): Use an HTTPS connection to the server</span>
<span class="sd"> use_ssl (bool): Controls the scheme prepended to any host that doesn&#39;t</span>
<span class="sd"> already include one (``https://`` when True, ``http://`` when</span>
<span class="sd"> False). Hosts that already carry a scheme are left unchanged.</span>
<span class="sd"> ssl_cert_path (str): Path to the certificate chain</span>
<span class="sd"> skip_certificate_verification (bool): Skip certificate verification</span>
<span class="sd"> username (str): The username to use for authentication</span>
@@ -419,9 +497,12 @@
<span class="n">_SERVERLESS</span> <span class="o">=</span> <span class="n">serverless</span>
<span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">hosts</span><span class="p">,</span> <span class="nb">list</span><span class="p">):</span>
<span class="n">hosts</span> <span class="o">=</span> <span class="p">[</span><span class="n">hosts</span><span class="p">]</span>
<span class="n">conn_params</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;hosts&quot;</span><span class="p">:</span> <span class="n">hosts</span><span class="p">,</span> <span class="s2">&quot;timeout&quot;</span><span class="p">:</span> <span class="n">timeout</span><span class="p">}</span>
<span class="n">scheme</span> <span class="o">=</span> <span class="s2">&quot;https://&quot;</span> <span class="k">if</span> <span class="n">use_ssl</span> <span class="k">else</span> <span class="s2">&quot;http://&quot;</span>
<span class="n">normalized_hosts</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">host</span> <span class="k">if</span> <span class="s2">&quot;://&quot;</span> <span class="ow">in</span> <span class="n">host</span> <span class="k">else</span> <span class="s2">&quot;</span><span class="si">{0}{1}</span><span class="s2">&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">scheme</span><span class="p">,</span> <span class="n">host</span><span class="p">)</span> <span class="k">for</span> <span class="n">host</span> <span class="ow">in</span> <span class="n">hosts</span>
<span class="p">]</span>
<span class="n">conn_params</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;hosts&quot;</span><span class="p">:</span> <span class="n">normalized_hosts</span><span class="p">,</span> <span class="s2">&quot;request_timeout&quot;</span><span class="p">:</span> <span class="n">timeout</span><span class="p">}</span>
<span class="k">if</span> <span class="n">use_ssl</span><span class="p">:</span>
<span class="n">conn_params</span><span class="p">[</span><span class="s2">&quot;use_ssl&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span>
<span class="k">if</span> <span class="n">ssl_cert_path</span><span class="p">:</span>
<span class="n">conn_params</span><span class="p">[</span><span class="s2">&quot;ca_certs&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">ssl_cert_path</span>
<span class="k">if</span> <span class="n">skip_certificate_verification</span><span class="p">:</span>
@@ -429,7 +510,7 @@
<span class="k">else</span><span class="p">:</span>
<span class="n">conn_params</span><span class="p">[</span><span class="s2">&quot;verify_certs&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span>
<span class="k">if</span> <span class="n">username</span> <span class="ow">and</span> <span class="n">password</span><span class="p">:</span>
<span class="n">conn_params</span><span class="p">[</span><span class="s2">&quot;http_auth&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="n">username</span><span class="p">,</span> <span class="n">password</span><span class="p">)</span>
<span class="n">conn_params</span><span class="p">[</span><span class="s2">&quot;basic_auth&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="n">username</span><span class="p">,</span> <span class="n">password</span><span class="p">)</span>
<span class="k">if</span> <span class="n">api_key</span><span class="p">:</span>
<span class="n">conn_params</span><span class="p">[</span><span class="s2">&quot;api_key&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">api_key</span>
<span class="n">connections</span><span class="o">.</span><span class="n">create_connection</span><span class="p">(</span><span class="o">**</span><span class="n">conn_params</span><span class="p">)</span></div>
@@ -481,43 +562,21 @@
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> Updates index mappings</span>
<span class="sd"> This is a no-op kept for API compatibility (``cli.py`` calls it on</span>
<span class="sd"> startup). The only migration this function ever performed was</span>
<span class="sd"> re-typing ``published_policy.fo`` from ``long`` to ``text``, which</span>
<span class="sd"> applied exclusively to indices still carrying the legacy</span>
<span class="sd"> Elasticsearch 6-era ``&quot;doc&quot;`` mapping type. The 8.x client can only</span>
<span class="sd"> reach servers (Elasticsearch 8.x/9.x) whose indices were created on</span>
<span class="sd"> Elasticsearch 7.x or later and are therefore typeless, so that</span>
<span class="sd"> migration path is unreachable and has been removed.</span>
<span class="sd"> Args:</span>
<span class="sd"> aggregate_indexes (list): A list of aggregate index names</span>
<span class="sd"> (accepted for API compatibility; unused)</span>
<span class="sd"> failure_indexes (list): A list of failure index names</span>
<span class="sd"> (accepted for API compatibility; no migrations are</span>
<span class="sd"> currently needed for failure indexes)</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">version</span> <span class="o">=</span> <span class="mi">2</span>
<span class="k">if</span> <span class="n">aggregate_indexes</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">aggregate_indexes</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">aggregate_index_name</span> <span class="ow">in</span> <span class="n">aggregate_indexes</span><span class="p">:</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">Index</span><span class="p">(</span><span class="n">aggregate_index_name</span><span class="p">)</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
<span class="k">continue</span>
<span class="n">aggregate_index</span> <span class="o">=</span> <span class="n">Index</span><span class="p">(</span><span class="n">aggregate_index_name</span><span class="p">)</span>
<span class="n">doc</span> <span class="o">=</span> <span class="s2">&quot;doc&quot;</span>
<span class="n">fo_field</span> <span class="o">=</span> <span class="s2">&quot;published_policy.fo&quot;</span>
<span class="n">fo</span> <span class="o">=</span> <span class="s2">&quot;fo&quot;</span>
<span class="n">fo_mapping</span> <span class="o">=</span> <span class="n">aggregate_index</span><span class="o">.</span><span class="n">get_field_mapping</span><span class="p">(</span><span class="n">fields</span><span class="o">=</span><span class="p">[</span><span class="n">fo_field</span><span class="p">])</span>
<span class="n">fo_mapping</span> <span class="o">=</span> <span class="n">fo_mapping</span><span class="p">[</span><span class="nb">list</span><span class="p">(</span><span class="n">fo_mapping</span><span class="o">.</span><span class="n">keys</span><span class="p">())[</span><span class="mi">0</span><span class="p">]][</span><span class="s2">&quot;mappings&quot;</span><span class="p">]</span>
<span class="k">if</span> <span class="n">doc</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">fo_mapping</span><span class="p">:</span>
<span class="k">continue</span>
<span class="n">fo_mapping</span> <span class="o">=</span> <span class="n">fo_mapping</span><span class="p">[</span><span class="n">doc</span><span class="p">][</span><span class="n">fo_field</span><span class="p">][</span><span class="s2">&quot;mapping&quot;</span><span class="p">][</span><span class="n">fo</span><span class="p">]</span>
<span class="n">fo_type</span> <span class="o">=</span> <span class="n">fo_mapping</span><span class="p">[</span><span class="s2">&quot;type&quot;</span><span class="p">]</span>
<span class="k">if</span> <span class="n">fo_type</span> <span class="o">==</span> <span class="s2">&quot;long&quot;</span><span class="p">:</span>
<span class="n">new_index_name</span> <span class="o">=</span> <span class="s2">&quot;</span><span class="si">{0}</span><span class="s2">-v</span><span class="si">{1}</span><span class="s2">&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">aggregate_index_name</span><span class="p">,</span> <span class="n">version</span><span class="p">)</span>
<span class="n">body</span> <span class="o">=</span> <span class="p">{</span>
<span class="s2">&quot;properties&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="s2">&quot;published_policy.fo&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="s2">&quot;type&quot;</span><span class="p">:</span> <span class="s2">&quot;text&quot;</span><span class="p">,</span>
<span class="s2">&quot;fields&quot;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&quot;keyword&quot;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&quot;type&quot;</span><span class="p">:</span> <span class="s2">&quot;keyword&quot;</span><span class="p">,</span> <span class="s2">&quot;ignore_above&quot;</span><span class="p">:</span> <span class="mi">256</span><span class="p">}},</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">Index</span><span class="p">(</span><span class="n">new_index_name</span><span class="p">)</span><span class="o">.</span><span class="n">create</span><span class="p">()</span>
<span class="n">Index</span><span class="p">(</span><span class="n">new_index_name</span><span class="p">)</span><span class="o">.</span><span class="n">put_mapping</span><span class="p">(</span><span class="n">doc_type</span><span class="o">=</span><span class="n">doc</span><span class="p">,</span> <span class="n">body</span><span class="o">=</span><span class="n">body</span><span class="p">)</span>
<span class="n">reindex</span><span class="p">(</span><span class="n">connections</span><span class="o">.</span><span class="n">get_connection</span><span class="p">(),</span> <span class="n">aggregate_index_name</span><span class="p">,</span> <span class="n">new_index_name</span><span class="p">)</span> <span class="c1"># pyright: ignore[reportArgumentType]</span>
<span class="n">Index</span><span class="p">(</span><span class="n">aggregate_index_name</span><span class="p">)</span><span class="o">.</span><span class="n">delete</span><span class="p">()</span></div>
<span class="sd"> (accepted for API compatibility; unused)</span>
<span class="sd"> &quot;&quot;&quot;</span></div>
@@ -574,7 +633,10 @@
<span class="n">search</span> <span class="o">=</span> <span class="n">Search</span><span class="p">(</span><span class="n">index</span><span class="o">=</span><span class="n">search_index</span><span class="p">)</span>
<span class="n">query</span> <span class="o">=</span> <span class="n">org_name_query</span> <span class="o">&amp;</span> <span class="n">report_id_query</span> <span class="o">&amp;</span> <span class="n">domain_query</span>
<span class="n">query</span> <span class="o">=</span> <span class="n">query</span> <span class="o">&amp;</span> <span class="n">begin_date_query</span> <span class="o">&amp;</span> <span class="n">end_date_query</span>
<span class="n">search</span><span class="o">.</span><span class="n">query</span> <span class="o">=</span> <span class="n">query</span>
<span class="c1"># elasticsearch.dsl&#39;s own docs recommend ``search.query = Q(...)``, but</span>
<span class="c1"># the ProxyDescriptor.__set__ stub is typed to accept only</span>
<span class="c1"># Dict[str, Any], not the Query object the docs tell you to assign.</span>
<span class="n">search</span><span class="o">.</span><span class="n">query</span> <span class="o">=</span> <span class="n">query</span> <span class="c1"># pyright: ignore[reportAttributeAccessIssue]</span>
<span class="n">begin_date_human</span> <span class="o">=</span> <span class="n">begin_date</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s2">&quot;%Y-%m-</span><span class="si">%d</span><span class="s2"> %H:%M:%SZ&quot;</span><span class="p">)</span>
<span class="n">end_date_human</span> <span class="o">=</span> <span class="n">end_date</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s2">&quot;%Y-%m-</span><span class="si">%d</span><span class="s2"> %H:%M:%SZ&quot;</span><span class="p">)</span>
@@ -803,7 +865,7 @@
<span class="n">subject_query</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;match_phrase&quot;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&quot;sample.headers.subject&quot;</span><span class="p">:</span> <span class="n">subject</span><span class="p">}}</span>
<span class="n">q</span> <span class="o">=</span> <span class="n">q</span> <span class="o">&amp;</span> <span class="n">Q</span><span class="p">(</span><span class="n">subject_query</span><span class="p">)</span> <span class="c1"># pyright: ignore[reportArgumentType]</span>
<span class="n">search</span><span class="o">.</span><span class="n">query</span> <span class="o">=</span> <span class="n">q</span>
<span class="n">search</span><span class="o">.</span><span class="n">query</span> <span class="o">=</span> <span class="n">q</span> <span class="c1"># pyright: ignore[reportAttributeAccessIssue]</span>
<span class="n">existing</span> <span class="o">=</span> <span class="n">search</span><span class="o">.</span><span class="n">execute</span><span class="p">()</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">existing</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span>
@@ -947,7 +1009,7 @@
<span class="n">search</span> <span class="o">=</span> <span class="n">Search</span><span class="p">(</span><span class="n">index</span><span class="o">=</span><span class="n">search_index</span><span class="p">)</span>
<span class="n">query</span> <span class="o">=</span> <span class="n">org_name_query</span> <span class="o">&amp;</span> <span class="n">report_id_query</span>
<span class="n">query</span> <span class="o">=</span> <span class="n">query</span> <span class="o">&amp;</span> <span class="n">begin_date_query</span> <span class="o">&amp;</span> <span class="n">end_date_query</span>
<span class="n">search</span><span class="o">.</span><span class="n">query</span> <span class="o">=</span> <span class="n">query</span>
<span class="n">search</span><span class="o">.</span><span class="n">query</span> <span class="o">=</span> <span class="n">query</span> <span class="c1"># pyright: ignore[reportAttributeAccessIssue]</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">existing</span> <span class="o">=</span> <span class="n">search</span><span class="o">.</span><span class="n">execute</span><span class="p">()</span>
@@ -1037,7 +1099,7 @@
<span class="n">additional_information_uri</span><span class="o">=</span><span class="n">additional_information_uri</span><span class="p">,</span>
<span class="n">failure_reason_code</span><span class="o">=</span><span class="n">failure_reason_code</span><span class="p">,</span>
<span class="p">)</span>
<span class="n">smtp_tls_doc</span><span class="o">.</span><span class="n">policies</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">policy_doc</span><span class="p">)</span> <span class="c1"># pyright: ignore[reportCallIssue]</span>
<span class="n">smtp_tls_doc</span><span class="o">.</span><span class="n">policies</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">policy_doc</span><span class="p">)</span>
<span class="n">create_indexes</span><span class="p">([</span><span class="n">index</span><span class="p">],</span> <span class="n">index_settings</span><span class="p">)</span>
<span class="n">smtp_tls_doc</span><span class="o">.</span><span class="n">meta</span><span class="o">.</span><span class="n">index</span> <span class="o">=</span> <span class="n">index</span> <span class="c1"># pyright: ignore[reportOptionalMemberAccess, reportAttributeAccessIssue]</span>
+2 -2
View File
@@ -5,14 +5,14 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>parsedmarc.opensearch &mdash; parsedmarc 10.2.2 documentation</title>
<title>parsedmarc.opensearch &mdash; parsedmarc 10.2.4 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css?v=b86133f3" />
<link rel="stylesheet" type="text/css" href="../../_static/css/theme.css?v=9edc463e" />
<script src="../../_static/jquery.js?v=5d32c60e"></script>
<script src="../../_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
<script src="../../_static/documentation_options.js?v=43731225"></script>
<script src="../../_static/documentation_options.js?v=2c8206f1"></script>
<script src="../../_static/doctools.js?v=fd6eb6e6"></script>
<script src="../../_static/sphinx_highlight.js?v=6ffebe34"></script>
<script src="../../_static/js/theme.js"></script>
+11 -13
View File
@@ -5,14 +5,14 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>parsedmarc.splunk &mdash; parsedmarc 10.2.2 documentation</title>
<title>parsedmarc.splunk &mdash; parsedmarc 10.2.4 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css?v=b86133f3" />
<link rel="stylesheet" type="text/css" href="../../_static/css/theme.css?v=9edc463e" />
<script src="../../_static/jquery.js?v=5d32c60e"></script>
<script src="../../_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
<script src="../../_static/documentation_options.js?v=43731225"></script>
<script src="../../_static/documentation_options.js?v=2c8206f1"></script>
<script src="../../_static/doctools.js?v=fd6eb6e6"></script>
<script src="../../_static/sphinx_highlight.js?v=6ffebe34"></script>
<script src="../../_static/js/theme.js"></script>
@@ -89,15 +89,12 @@
<span class="kn">from</span><span class="w"> </span><span class="nn">typing</span><span class="w"> </span><span class="kn">import</span> <span class="n">Any</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">urllib.parse</span><span class="w"> </span><span class="kn">import</span> <span class="n">urlparse</span>
<span class="kn">import</span><span class="w"> </span><span class="nn">requests</span>
<span class="kn">import</span><span class="w"> </span><span class="nn">urllib3</span>
<span class="kn">import</span><span class="w"> </span><span class="nn">httpx</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">parsedmarc.constants</span><span class="w"> </span><span class="kn">import</span> <span class="n">USER_AGENT</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">parsedmarc.log</span><span class="w"> </span><span class="kn">import</span> <span class="n">logger</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">parsedmarc.utils</span><span class="w"> </span><span class="kn">import</span> <span class="n">human_timestamp_to_unix_timestamp</span>
<span class="n">urllib3</span><span class="o">.</span><span class="n">disable_warnings</span><span class="p">(</span><span class="n">urllib3</span><span class="o">.</span><span class="n">exceptions</span><span class="o">.</span><span class="n">InsecureRequestWarning</span><span class="p">)</span>
<div class="viewcode-block" id="SplunkError">
<a class="viewcode-back" href="../../api.html#parsedmarc.splunk.SplunkError">[docs]</a>
@@ -143,18 +140,19 @@
<span class="bp">self</span><span class="o">.</span><span class="n">index</span> <span class="o">=</span> <span class="n">index</span>
<span class="bp">self</span><span class="o">.</span><span class="n">host</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">getfqdn</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">source</span> <span class="o">=</span> <span class="n">source</span>
<span class="bp">self</span><span class="o">.</span><span class="n">session</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">Session</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">timeout</span> <span class="o">=</span> <span class="n">timeout</span>
<span class="bp">self</span><span class="o">.</span><span class="n">verify</span> <span class="o">=</span> <span class="n">verify</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_common_data</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span> <span class="o">|</span> <span class="nb">int</span> <span class="o">|</span> <span class="nb">float</span> <span class="o">|</span> <span class="nb">dict</span><span class="p">]</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">(</span>
<span class="n">host</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">host</span><span class="p">,</span> <span class="n">source</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">source</span><span class="p">,</span> <span class="n">index</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">index</span>
<span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">headers</span><span class="o">.</span><span class="n">update</span><span class="p">(</span>
<span class="p">{</span>
<span class="bp">self</span><span class="o">.</span><span class="n">session</span> <span class="o">=</span> <span class="n">httpx</span><span class="o">.</span><span class="n">Client</span><span class="p">(</span>
<span class="n">headers</span><span class="o">=</span><span class="p">{</span>
<span class="s2">&quot;User-Agent&quot;</span><span class="p">:</span> <span class="n">USER_AGENT</span><span class="p">,</span>
<span class="s2">&quot;Authorization&quot;</span><span class="p">:</span> <span class="s2">&quot;Splunk </span><span class="si">{0}</span><span class="s2">&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">access_token</span><span class="p">),</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="n">verify</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">verify</span><span class="p">,</span>
<span class="n">follow_redirects</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
<span class="p">)</span>
<div class="viewcode-block" id="HECClient.save_aggregate_reports_to_splunk">
@@ -224,7 +222,7 @@
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Skipping certificate verification for Splunk HEC&quot;</span><span class="p">)</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">response</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">post</span><span class="p">(</span>
<span class="bp">self</span><span class="o">.</span><span class="n">url</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="n">json_str</span><span class="p">,</span> <span class="n">verify</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">verify</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">timeout</span>
<span class="bp">self</span><span class="o">.</span><span class="n">url</span><span class="p">,</span> <span class="n">content</span><span class="o">=</span><span class="n">json_str</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">timeout</span>
<span class="p">)</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
@@ -270,7 +268,7 @@
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Skipping certificate verification for Splunk HEC&quot;</span><span class="p">)</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">response</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">post</span><span class="p">(</span>
<span class="bp">self</span><span class="o">.</span><span class="n">url</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="n">json_str</span><span class="p">,</span> <span class="n">verify</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">verify</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">timeout</span>
<span class="bp">self</span><span class="o">.</span><span class="n">url</span><span class="p">,</span> <span class="n">content</span><span class="o">=</span><span class="n">json_str</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">timeout</span>
<span class="p">)</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
@@ -312,7 +310,7 @@
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Skipping certificate verification for Splunk HEC&quot;</span><span class="p">)</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">response</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">post</span><span class="p">(</span>
<span class="bp">self</span><span class="o">.</span><span class="n">url</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="n">json_str</span><span class="p">,</span> <span class="n">verify</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">verify</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">timeout</span>
<span class="bp">self</span><span class="o">.</span><span class="n">url</span><span class="p">,</span> <span class="n">content</span><span class="o">=</span><span class="n">json_str</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">timeout</span>
<span class="p">)</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
+2 -2
View File
@@ -5,14 +5,14 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>parsedmarc.types &mdash; parsedmarc 10.2.2 documentation</title>
<title>parsedmarc.types &mdash; parsedmarc 10.2.4 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css?v=b86133f3" />
<link rel="stylesheet" type="text/css" href="../../_static/css/theme.css?v=9edc463e" />
<script src="../../_static/jquery.js?v=5d32c60e"></script>
<script src="../../_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
<script src="../../_static/documentation_options.js?v=43731225"></script>
<script src="../../_static/documentation_options.js?v=2c8206f1"></script>
<script src="../../_static/doctools.js?v=fd6eb6e6"></script>
<script src="../../_static/sphinx_highlight.js?v=6ffebe34"></script>
<script src="../../_static/js/theme.js"></script>
+23 -13
View File
@@ -5,14 +5,14 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>parsedmarc.utils &mdash; parsedmarc 10.2.2 documentation</title>
<title>parsedmarc.utils &mdash; parsedmarc 10.2.4 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css?v=b86133f3" />
<link rel="stylesheet" type="text/css" href="../../_static/css/theme.css?v=9edc463e" />
<script src="../../_static/jquery.js?v=5d32c60e"></script>
<script src="../../_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
<script src="../../_static/documentation_options.js?v=43731225"></script>
<script src="../../_static/documentation_options.js?v=2c8206f1"></script>
<script src="../../_static/doctools.js?v=fd6eb6e6"></script>
<script src="../../_static/sphinx_highlight.js?v=6ffebe34"></script>
<script src="../../_static/js/theme.js"></script>
@@ -110,9 +110,9 @@
<span class="kn">import</span><span class="w"> </span><span class="nn">dns.exception</span>
<span class="kn">import</span><span class="w"> </span><span class="nn">dns.resolver</span>
<span class="kn">import</span><span class="w"> </span><span class="nn">dns.reversename</span>
<span class="kn">import</span><span class="w"> </span><span class="nn">httpx</span>
<span class="kn">import</span><span class="w"> </span><span class="nn">maxminddb</span>
<span class="kn">import</span><span class="w"> </span><span class="nn">publicsuffixlist</span>
<span class="kn">import</span><span class="w"> </span><span class="nn">requests</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">dateutil.parser</span><span class="w"> </span><span class="kn">import</span> <span class="n">parse</span> <span class="k">as</span> <span class="n">parse_date</span>
<span class="kn">import</span><span class="w"> </span><span class="nn">parsedmarc.resources.ipinfo</span>
@@ -187,10 +187,12 @@
<span class="k">try</span><span class="p">:</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Trying to fetch PSL overrides from </span><span class="si">{</span><span class="n">url</span><span class="si">}</span><span class="s2">...&quot;</span><span class="p">)</span>
<span class="n">headers</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;User-Agent&quot;</span><span class="p">:</span> <span class="n">USER_AGENT</span><span class="p">}</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">)</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">httpx</span><span class="o">.</span><span class="n">get</span><span class="p">(</span>
<span class="n">url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">60</span><span class="p">,</span> <span class="n">follow_redirects</span><span class="o">=</span><span class="kc">True</span>
<span class="p">)</span>
<span class="n">response</span><span class="o">.</span><span class="n">raise_for_status</span><span class="p">()</span>
<span class="n">_load_text</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
<span class="k">except</span> <span class="n">requests</span><span class="o">.</span><span class="n">exceptions</span><span class="o">.</span><span class="n">RequestException</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="k">except</span> <span class="n">httpx</span><span class="o">.</span><span class="n">HTTPError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">logger</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Failed to fetch PSL overrides: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">psl_overrides</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
@@ -570,7 +572,9 @@
<span class="k">try</span><span class="p">:</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Trying to fetch IP database from </span><span class="si">{</span><span class="n">url</span><span class="si">}</span><span class="s2">...&quot;</span><span class="p">)</span>
<span class="n">headers</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;User-Agent&quot;</span><span class="p">:</span> <span class="n">USER_AGENT</span><span class="p">}</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">60</span><span class="p">)</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">httpx</span><span class="o">.</span><span class="n">get</span><span class="p">(</span>
<span class="n">url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">60</span><span class="p">,</span> <span class="n">follow_redirects</span><span class="o">=</span><span class="kc">True</span>
<span class="p">)</span>
<span class="n">response</span><span class="o">.</span><span class="n">raise_for_status</span><span class="p">()</span>
<span class="n">os</span><span class="o">.</span><span class="n">makedirs</span><span class="p">(</span><span class="n">cache_dir</span><span class="p">,</span> <span class="n">exist_ok</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="n">tmp_path</span> <span class="o">=</span> <span class="n">cached_path</span> <span class="o">+</span> <span class="s2">&quot;.tmp&quot;</span>
@@ -580,7 +584,7 @@
<span class="n">_IP_DB_PATH</span> <span class="o">=</span> <span class="n">cached_path</span>
<span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;IP database updated successfully&quot;</span><span class="p">)</span>
<span class="k">return</span>
<span class="k">except</span> <span class="n">requests</span><span class="o">.</span><span class="n">exceptions</span><span class="o">.</span><span class="n">RequestException</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="k">except</span> <span class="n">httpx</span><span class="o">.</span><span class="n">HTTPError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">logger</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Failed to fetch IP database: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">logger</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Failed to save IP database: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
@@ -676,10 +680,14 @@
<span class="n">params</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;token&quot;</span><span class="p">:</span> <span class="n">_IPINFO_API_TOKEN</span><span class="p">}</span>
<span class="n">headers</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;User-Agent&quot;</span><span class="p">:</span> <span class="n">USER_AGENT</span><span class="p">,</span> <span class="s2">&quot;Accept&quot;</span><span class="p">:</span> <span class="s2">&quot;application/json&quot;</span><span class="p">}</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span>
<span class="n">url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">,</span> <span class="n">params</span><span class="o">=</span><span class="n">params</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="n">_IPINFO_API_TIMEOUT</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">httpx</span><span class="o">.</span><span class="n">get</span><span class="p">(</span>
<span class="n">url</span><span class="p">,</span>
<span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">,</span>
<span class="n">params</span><span class="o">=</span><span class="n">params</span><span class="p">,</span>
<span class="n">timeout</span><span class="o">=</span><span class="n">_IPINFO_API_TIMEOUT</span><span class="p">,</span>
<span class="n">follow_redirects</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
<span class="p">)</span>
<span class="k">except</span> <span class="n">requests</span><span class="o">.</span><span class="n">exceptions</span><span class="o">.</span><span class="n">RequestException</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="k">except</span> <span class="n">httpx</span><span class="o">.</span><span class="n">HTTPError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;IPinfo API request for </span><span class="si">{</span><span class="n">ip_address</span><span class="si">}</span><span class="s2"> failed: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="k">return</span> <span class="kc">None</span>
@@ -687,7 +695,7 @@
<span class="k">raise</span> <span class="n">InvalidIPinfoAPIKey</span><span class="p">(</span>
<span class="sa">f</span><span class="s2">&quot;IPinfo API rejected the configured token (HTTP </span><span class="si">{</span><span class="n">response</span><span class="o">.</span><span class="n">status_code</span><span class="si">}</span><span class="s2">)&quot;</span>
<span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">response</span><span class="o">.</span><span class="n">ok</span><span class="p">:</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">response</span><span class="o">.</span><span class="n">is_success</span><span class="p">:</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span>
<span class="sa">f</span><span class="s2">&quot;IPinfo API returned HTTP </span><span class="si">{</span><span class="n">response</span><span class="o">.</span><span class="n">status_code</span><span class="si">}</span><span class="s2"> for </span><span class="si">{</span><span class="n">ip_address</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="p">)</span>
@@ -938,12 +946,14 @@
<span class="k">try</span><span class="p">:</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Trying to fetch reverse DNS map from </span><span class="si">{</span><span class="n">url</span><span class="si">}</span><span class="s2">...&quot;</span><span class="p">)</span>
<span class="n">headers</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;User-Agent&quot;</span><span class="p">:</span> <span class="n">USER_AGENT</span><span class="p">}</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">)</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">httpx</span><span class="o">.</span><span class="n">get</span><span class="p">(</span>
<span class="n">url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">60</span><span class="p">,</span> <span class="n">follow_redirects</span><span class="o">=</span><span class="kc">True</span>
<span class="p">)</span>
<span class="n">response</span><span class="o">.</span><span class="n">raise_for_status</span><span class="p">()</span>
<span class="n">csv_file</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
<span class="n">csv_file</span><span class="o">.</span><span class="n">seek</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="n">load_csv</span><span class="p">(</span><span class="n">csv_file</span><span class="p">)</span>
<span class="k">except</span> <span class="n">requests</span><span class="o">.</span><span class="n">exceptions</span><span class="o">.</span><span class="n">RequestException</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="k">except</span> <span class="n">httpx</span><span class="o">.</span><span class="n">HTTPError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">logger</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Failed to fetch reverse DNS map: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
<span class="n">logger</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s2">&quot;Not a valid CSV file&quot;</span><span class="p">)</span>
+4 -1
View File
@@ -3,7 +3,10 @@
To set up visual dashboards of DMARC data, install Elasticsearch and Kibana.
:::{note}
Elasticsearch and Kibana 6 or later are required
Elasticsearch and Kibana 8 or later are required (parsedmarc's 8.x Python
client also supports Elasticsearch 9). OpenSearch users must use the
`[opensearch]` configuration section instead — the Elasticsearch 8.x client
refuses to connect to non-Elasticsearch clusters.
:::
## Installation
+169 -5
View File
@@ -249,12 +249,44 @@ The full set of configuration options are:
could be a shared mailbox if the user has access to the mailbox
- `graph_url` - str: Microsoft Graph URL. Allows for use of National Clouds (ex Azure Gov)
(Default: https://graph.microsoft.com)
:::{warning}
Setting `graph_url` alone is **not** sufficient for a national/sovereign
cloud tenant. It only changes the Microsoft Graph API root; the
Microsoft Entra ID (Azure AD) token endpoint used for authentication is
not currently configurable in parsedmarc or `mailsuite`, and always
defaults to the global `https://login.microsoftonline.com`. A true
national-cloud deployment also needs its own Entra ID endpoint, so
`graph_url` by itself only helps if your tenant is registered in the
global cloud but you specifically need to reach one of these Graph API
roots (per [Microsoft's national cloud deployment docs][ms-graph-clouds]):
| National cloud | Microsoft Graph URL | Entra ID endpoint (not configurable here) |
|---|---|---|
| Global (default) | `https://graph.microsoft.com` | `https://login.microsoftonline.com` |
| US Government L4 (GCC High) | `https://graph.microsoft.us` | `https://login.microsoftonline.us` |
| US Government L5 (DoD) | `https://dod-graph.microsoft.us` | `https://login.microsoftonline.us` |
| China, operated by 21Vianet | `https://microsoftgraph.chinacloudapi.cn` | `https://login.chinacloudapi.cn` |
[ms-graph-clouds]: https://learn.microsoft.com/en-us/graph/deployments
:::
- `token_file` - str: Path to save the token file
(Default: `.token`)
- `allow_unencrypted_storage` - bool: Allows the Azure Identity
module to fall back to unencrypted token cache (Default: `False`).
Even if enabled, the cache will always try encrypted storage first.
:::{note}
`token_file` stores the serialized authentication record; the
underlying MSAL persistent token cache is separately named
`parsedmarc`, not `mailsuite`'s own default cache name. This is
deliberate: the Graph mailbox backend used to live directly in
parsedmarc, and moved into the `mailsuite` dependency in parsedmarc
9.11.0. Explicitly keeping the `parsedmarc` cache name means tokens
cached before that move keep working after upgrading — there is
nothing to migrate and no action needed on your part.
:::
:::{note}
You must create an app registration in Azure AD and have an
admin grant the Microsoft Graph `Mail.ReadWrite`
@@ -263,6 +295,11 @@ The full set of configuration options are:
username, you must grant the app `Mail.ReadWrite.Shared`.
:::
| Auth method | Reading (own mailbox) | Reading (shared mailbox) |
|---|---|---|
| `UsernamePassword` / `DeviceCode` (delegated) | `Mail.ReadWrite` | `Mail.ReadWrite.Shared` |
| `ClientSecret` / `Certificate` / `ClientAssertion` (application) | `Mail.ReadWrite` (application), scoped via `New-ApplicationAccessPolicy` | same as own mailbox — app-only access is scoped by policy, not by permission name |
:::{tip}
**Troubleshooting connections.** Run with `--verbose` to log a
redacted connection summary (auth method, tenant, client ID,
@@ -273,6 +310,11 @@ The full set of configuration options are:
an Exchange Online-side one), Microsoft Graph SDK requests, and
`httpx` HTTP request lines. Secret values (passwords, client
secrets, certificate passwords) are never written to logs.
Connection, mailbox fetch, message send, and `--watch` failures
each log a single ERROR line naming the mailbox, tenant, auth
method, and the Graph `request-id`/`client-request-id` when
available — worth quoting verbatim when contacting Microsoft
support.
:::
:::{warning}
@@ -295,6 +337,120 @@ The full set of configuration options are:
applies to the `Certificate` and `ClientAssertion` auth methods.
:::
**Sending the summary email via Microsoft Graph.**
When `[msgraph]` is configured and `[smtp]` has a `to` value but no
`host`, the periodic summary email is sent through the same
already-authenticated Graph mailbox connection used for reading
(`/users/{mailbox}/sendMail`), and a copy is saved to Sent Items.
When `[smtp] host` is set, SMTP is used regardless of whether
`[msgraph]` is also configured — SMTP is always preferred, with no
automatic fallback to Graph on SMTP failure.
Example config combining `[msgraph]` with a `[smtp]` section that
only sets `to`/`subject` (no `host`):
```ini
[msgraph]
auth_method = Certificate
client_id = ...
tenant_id = ...
mailbox = dmarc-reports@example.com
certificate_path = /path/to/cert.pem
[smtp]
to = admin@example.com
subject = DMARC Summary
```
Required Microsoft Graph permissions, in addition to the
reading-related permissions documented above:
| Auth method | Reading | Sending (own mailbox) | Sending (shared mailbox) |
|---|---|---|---|
| `UsernamePassword` / `DeviceCode` (delegated) | `Mail.ReadWrite` (+`.Shared` for shared) | `Mail.Send` | `Mail.Send.Shared` |
| `ClientSecret` / `Certificate` / `ClientAssertion` (application) | `Mail.ReadWrite` (application) | `Mail.Send` (application) | `Mail.Send` (application), scoped via `New-ApplicationAccessPolicy` |
:::{warning}
Graph-based sending is only confirmed to work with the app-only
auth methods (`ClientSecret`, `Certificate`, `ClientAssertion`).
The delegated auth methods (`UsernamePassword`, `DeviceCode`)
currently request only the `Mail.ReadWrite`(`.Shared`) scope when
authenticating, not `Mail.Send` — so a delegated connection's
access token will not carry `Mail.Send` even if an administrator
has granted it, and `/sendMail` calls are expected to fail with an
access-denied error regardless of what's granted in Azure AD. Use
an app-only auth method if you need Graph-based sending.
:::
**Minimal example configs.** Each auth method needs a different
minimum set of keys. These read-only examples omit `[smtp]`; see
above for adding Graph-based sending on top of any of them.
`UsernamePassword` (delegated, own mailbox):
```ini
[msgraph]
auth_method = UsernamePassword
client_id = ...
client_secret = ...
user = dmarc-reports@example.com
password = ...
```
`DeviceCode` (delegated, interactive sign-in on first run — `user`
is the account that signs in; `mailbox` is the shared mailbox it
reads, and only needs to differ from `user` to request
`Mail.ReadWrite.Shared` instead of plain `Mail.ReadWrite`):
```ini
[msgraph]
auth_method = DeviceCode
client_id = ...
tenant_id = ...
user = signing-in-user@example.com
mailbox = dmarc-reports@example.com
```
`ClientSecret` (app-only):
```ini
[msgraph]
auth_method = ClientSecret
client_id = ...
tenant_id = ...
client_secret = ...
mailbox = dmarc-reports@example.com
```
`Certificate` (app-only):
```ini
[msgraph]
auth_method = Certificate
client_id = ...
tenant_id = ...
certificate_path = /path/to/cert.pem
mailbox = dmarc-reports@example.com
```
`ClientAssertion` (app-only, short-lived JWT — see the note above
about its unsuitability for `watch` mode):
```ini
[msgraph]
auth_method = ClientAssertion
client_id = ...
tenant_id = ...
client_assertion = ...
mailbox = dmarc-reports@example.com
```
:::{tip}
**Troubleshooting.**
| Error | Cause | Fix |
|---|---|---|
| *"...needs permission to access resources in your organization that only an admin can grant"* / "Admin consent required" | A delegated auth method (`UsernamePassword`, `DeviceCode`) is authenticating with a scope (`Mail.ReadWrite` or `Mail.ReadWrite.Shared`) the tenant admin hasn't consented to yet. | Have an Entra ID admin grant consent: Azure Portal → **Enterprise Applications** → *your app* → **Permissions** → **Grant admin consent**, or `az ad app permission admin-consent --id <CLIENT_ID>`. This is separate from the `New-ApplicationAccessPolicy` step above, which only applies to app-only auth. |
| `ErrorItemNotFound: ... Default folder Root not found` | `mailsuite` can resolve the well-known folders (`Inbox`, `Archive`, `Drafts`, `Sent Items`, `Deleted Items`, `Junk Email`) even when a mailbox's folder hierarchy hasn't fully provisioned, but a **custom, non-well-known** `reports_folder` name still fails to resolve on such a mailbox. | Point `reports_folder` at (or under) one of the six well-known folder names above, or sign into the (shared) mailbox once via Outlook/OWA to force Exchange to provision it, then retry. |
| `RuntimeError: Event loop is closed` | Historical bug, fixed in `mailsuite` 2.0.2. Not reachable with the `mailsuite>=2.2.2` this project requires. | Confirm your installed `mailsuite` version is current (`pip show mailsuite`); upgrade if it's somehow pinned below 2.0.2. |
| Invalid/rejected timestamp in the `since`/`receivedDateTime` filter | Historical bug (parsedmarc [#706](https://github.com/domainaware/parsedmarc/pull/706)/[#708](https://github.com/domainaware/parsedmarc/pull/708)): older versions appended a spurious `Z` to an already-UTC-offset ISO timestamp. Fixed since parsedmarc 9.5.1/9.5.5. | Upgrade parsedmarc if you're on a version older than 9.5.5. |
:::
- `elasticsearch`
- `hosts` - str: A comma separated list of hostnames and ports
or URLs (e.g. `127.0.0.1:9200` or
@@ -371,20 +527,28 @@ The full set of configuration options are:
- `aggregate_topic` - str: The Kafka topic for aggregate reports
- `failure_topic` - str: The Kafka topic for failure reports
- `smtp`
- `host` - str: The SMTP hostname
- `host` - str: The SMTP hostname. Required unless `[msgraph]` is
configured, in which case omitting it sends the summary via
Microsoft Graph instead — see "Sending the summary email via
Microsoft Graph" above.
- `port` - int: The SMTP port (Default: `25`)
- `ssl` - bool: Require SSL/TLS instead of using STARTTLS
- `skip_certificate_verification` - bool: Skip certificate
verification (not recommended)
- `user` - str: the SMTP username
- `password` - str: the SMTP password
- `from` - str: The From header to use in the email
- `user` - str: the SMTP username. SMTP-only; not used when sending
via Microsoft Graph.
- `password` - str: the SMTP password. SMTP-only; not used when
sending via Microsoft Graph.
- `from` - str: The From header to use in the email. SMTP-only.
When sent via Microsoft Graph, the message's `From` is always the
`[msgraph]` mailbox — `[smtp] from` has no effect.
- `to` - list: A list of email addresses to send to
- `subject` - str: The Subject header to use in the email
(Default: `parsedmarc report`)
- `attachment` - str: The ZIP attachment filenames
(Default: `DMARC-<YYYY-MM-DD>.zip`)
- `message` - str: The email message
(Default: `Please see the attached parsedmarc report.`)
(Default: `Please see the attached DMARC results.`)
:::{note}
`%` characters must be escaped with another `%` character,
+1 -1
View File
@@ -1,5 +1,5 @@
const DOCUMENTATION_OPTIONS = {
VERSION: '10.2.2',
VERSION: '10.2.4',
LANGUAGE: 'en',
COLLAPSE_INDEX: false,
BUILDER: 'html',
+40 -6
View File
@@ -6,14 +6,14 @@
<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>API reference &mdash; parsedmarc 10.2.2 documentation</title>
<title>API reference &mdash; parsedmarc 10.2.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=b86133f3" />
<link rel="stylesheet" type="text/css" href="_static/css/theme.css?v=9edc463e" />
<script src="_static/jquery.js?v=5d32c60e"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
<script src="_static/documentation_options.js?v=43731225"></script>
<script src="_static/documentation_options.js?v=2c8206f1"></script>
<script src="_static/doctools.js?v=fd6eb6e6"></script>
<script src="_static/sphinx_highlight.js?v=6ffebe34"></script>
<script src="_static/js/theme.js"></script>
@@ -63,6 +63,7 @@
<li class="toctree-l3"><a class="reference internal" href="#parsedmarc.ParserError"><code class="docutils literal notranslate"><span class="pre">ParserError</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="#parsedmarc.append_json"><code class="docutils literal notranslate"><span class="pre">append_json()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="#parsedmarc.email_results"><code class="docutils literal notranslate"><span class="pre">email_results()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="#parsedmarc.email_results_via_msgraph"><code class="docutils literal notranslate"><span class="pre">email_results_via_msgraph()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="#parsedmarc.extract_report"><code class="docutils literal notranslate"><span class="pre">extract_report()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="#parsedmarc.extract_report_from_file_path"><code class="docutils literal notranslate"><span class="pre">extract_report_from_file_path()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="#parsedmarc.get_dmarc_reports_from_mailbox"><code class="docutils literal notranslate"><span class="pre">get_dmarc_reports_from_mailbox()</span></code></a></li>
@@ -294,6 +295,29 @@ JSON array out of repeated appends.</p>
</dl>
</dd></dl>
<dl class="py function">
<dt class="sig sig-object py" id="parsedmarc.email_results_via_msgraph">
<span class="sig-prename descclassname"><span class="pre">parsedmarc.</span></span><span class="sig-name descname"><span class="pre">email_results_via_msgraph</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">results</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><a class="reference internal" href="#parsedmarc.types.ParsingResults" title="parsedmarc.types.ParsingResults"><span class="pre">ParsingResults</span></a></span></em>, <em class="sig-param"><span class="n"><span class="pre">connection</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">MSGraphConnection</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">mail_to</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">list</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span></span></em>, <em class="sig-param"><span class="keyword-only-separator o"><abbr title="Keyword-only parameters separator (PEP 3102)"><span class="pre">*</span></abbr></span></em>, <em class="sig-param"><span class="n"><span class="pre">mail_cc</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">list</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span><span class="w"> </span><span class="p"><span class="pre">|</span></span><span class="w"> </span><span class="pre">None</span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">mail_bcc</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">list</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span><span class="w"> </span><span class="p"><span class="pre">|</span></span><span class="w"> </span><span class="pre">None</span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">subject</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">str</span><span class="w"> </span><span class="p"><span class="pre">|</span></span><span class="w"> </span><span class="pre">None</span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">attachment_filename</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">str</span><span class="w"> </span><span class="p"><span class="pre">|</span></span><span class="w"> </span><span class="pre">None</span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">message</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">str</span><span class="w"> </span><span class="p"><span class="pre">|</span></span><span class="w"> </span><span class="pre">None</span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">None</span></span></span><a class="reference internal" href="_modules/parsedmarc.html#email_results_via_msgraph"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#parsedmarc.email_results_via_msgraph" title="Link to this definition"></a></dt>
<dd><p>Emails parsing results as a zip file via an already-authenticated
Microsoft Graph mailbox connection (<code class="docutils literal notranslate"><span class="pre">/users/{mailbox}/sendMail</span></code>),
saving a copy to Sent Items.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
<dd class="field-odd"><ul class="simple">
<li><p><strong>results</strong> (<em>dict</em>) Parsing results</p></li>
<li><p><strong>connection</strong> (<em>MSGraphConnection</em>) An already-authenticated Microsoft
Graph mailbox connection</p></li>
<li><p><strong>mail_to</strong> (<em>list</em>) A list of addresses to mail to</p></li>
<li><p><strong>mail_cc</strong> (<em>list</em>) A list of addresses to CC</p></li>
<li><p><strong>mail_bcc</strong> (<em>list</em>) A list addresses to BCC</p></li>
<li><p><strong>subject</strong> (<em>str</em>) Overrides the default message subject</p></li>
<li><p><strong>attachment_filename</strong> (<em>str</em>) Override the default attachment filename</p></li>
<li><p><strong>message</strong> (<em>str</em>) Override the default plain text body</p></li>
</ul>
</dd>
</dl>
</dd></dl>
<dl class="py function">
<dt class="sig sig-object py" id="parsedmarc.extract_report">
<span class="sig-prename descclassname"><span class="pre">parsedmarc.</span></span><span class="sig-name descname"><span class="pre">extract_report</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">content</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">bytes</span><span class="w"> </span><span class="p"><span class="pre">|</span></span><span class="w"> </span><span class="pre">str</span><span class="w"> </span><span class="p"><span class="pre">|</span></span><span class="w"> </span><span class="pre">BinaryIO</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">str</span></span></span><a class="reference internal" href="_modules/parsedmarc.html#extract_report"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#parsedmarc.extract_report" title="Link to this definition"></a></dt>
@@ -850,13 +874,21 @@ remaining keys are passed through; defaults are skipped entirely.</p></li>
<dt class="sig sig-object py" id="parsedmarc.elastic.migrate_indexes">
<span class="sig-prename descclassname"><span class="pre">parsedmarc.elastic.</span></span><span class="sig-name descname"><span class="pre">migrate_indexes</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">aggregate_indexes</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">list</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span><span class="w"> </span><span class="p"><span class="pre">|</span></span><span class="w"> </span><span class="pre">None</span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">failure_indexes</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">list</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span><span class="w"> </span><span class="p"><span class="pre">|</span></span><span class="w"> </span><span class="pre">None</span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/parsedmarc/elastic.html#migrate_indexes"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#parsedmarc.elastic.migrate_indexes" title="Link to this definition"></a></dt>
<dd><p>Updates index mappings</p>
<p>This is a no-op kept for API compatibility (<code class="docutils literal notranslate"><span class="pre">cli.py</span></code> calls it on
startup). The only migration this function ever performed was
re-typing <code class="docutils literal notranslate"><span class="pre">published_policy.fo</span></code> from <code class="docutils literal notranslate"><span class="pre">long</span></code> to <code class="docutils literal notranslate"><span class="pre">text</span></code>, which
applied exclusively to indices still carrying the legacy
Elasticsearch 6-era <code class="docutils literal notranslate"><span class="pre">&quot;doc&quot;</span></code> mapping type. The 8.x client can only
reach servers (Elasticsearch 8.x/9.x) whose indices were created on
Elasticsearch 7.x or later and are therefore typeless, so that
migration path is unreachable and has been removed.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
<dd class="field-odd"><ul class="simple">
<li><p><strong>aggregate_indexes</strong> (<em>list</em>) A list of aggregate index names</p></li>
<li><p><strong>aggregate_indexes</strong> (<em>list</em>) A list of aggregate index names
(accepted for API compatibility; unused)</p></li>
<li><p><strong>failure_indexes</strong> (<em>list</em>) A list of failure index names
(accepted for API compatibility; no migrations are
currently needed for failure indexes)</p></li>
(accepted for API compatibility; unused)</p></li>
</ul>
</dd>
</dl>
@@ -958,7 +990,9 @@ index</p></li>
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
<dd class="field-odd"><ul class="simple">
<li><p><strong>hosts</strong> (<em>str</em><em> | </em><em>list</em><em>[</em><em>str</em><em>]</em>) A single hostname or URL, or list of hostnames or URLs</p></li>
<li><p><strong>use_ssl</strong> (<em>bool</em>) Use an HTTPS connection to the server</p></li>
<li><p><strong>use_ssl</strong> (<em>bool</em>) Controls the scheme prepended to any host that doesnt
already include one (<code class="docutils literal notranslate"><span class="pre">https://</span></code> when True, <code class="docutils literal notranslate"><span class="pre">http://</span></code> when
False). Hosts that already carry a scheme are left unchanged.</p></li>
<li><p><strong>ssl_cert_path</strong> (<em>str</em>) Path to the certificate chain</p></li>
<li><p><strong>skip_certificate_verification</strong> (<em>bool</em>) Skip certificate verification</p></li>
<li><p><strong>username</strong> (<em>str</em>) The username to use for authentication</p></li>
+2 -2
View File
@@ -6,14 +6,14 @@
<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Contributing to parsedmarc &mdash; parsedmarc 10.2.2 documentation</title>
<title>Contributing to parsedmarc &mdash; parsedmarc 10.2.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=b86133f3" />
<link rel="stylesheet" type="text/css" href="_static/css/theme.css?v=9edc463e" />
<script src="_static/jquery.js?v=5d32c60e"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
<script src="_static/documentation_options.js?v=43731225"></script>
<script src="_static/documentation_options.js?v=2c8206f1"></script>
<script src="_static/doctools.js?v=fd6eb6e6"></script>
<script src="_static/sphinx_highlight.js?v=6ffebe34"></script>
<script src="_static/js/theme.js"></script>
+2 -2
View File
@@ -6,14 +6,14 @@
<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Accessing an inbox using OWA/EWS &mdash; parsedmarc 10.2.2 documentation</title>
<title>Accessing an inbox using OWA/EWS &mdash; parsedmarc 10.2.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=b86133f3" />
<link rel="stylesheet" type="text/css" href="_static/css/theme.css?v=9edc463e" />
<script src="_static/jquery.js?v=5d32c60e"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
<script src="_static/documentation_options.js?v=43731225"></script>
<script src="_static/documentation_options.js?v=2c8206f1"></script>
<script src="_static/doctools.js?v=fd6eb6e6"></script>
<script src="_static/sphinx_highlight.js?v=6ffebe34"></script>
<script src="_static/js/theme.js"></script>
+2 -2
View File
@@ -6,14 +6,14 @@
<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Understanding DMARC &mdash; parsedmarc 10.2.2 documentation</title>
<title>Understanding DMARC &mdash; parsedmarc 10.2.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=b86133f3" />
<link rel="stylesheet" type="text/css" href="_static/css/theme.css?v=9edc463e" />
<script src="_static/jquery.js?v=5d32c60e"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
<script src="_static/documentation_options.js?v=43731225"></script>
<script src="_static/documentation_options.js?v=2c8206f1"></script>
<script src="_static/doctools.js?v=fd6eb6e6"></script>
<script src="_static/sphinx_highlight.js?v=6ffebe34"></script>
<script src="_static/js/theme.js"></script>
+6 -3
View File
@@ -6,14 +6,14 @@
<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Elasticsearch and Kibana &mdash; parsedmarc 10.2.2 documentation</title>
<title>Elasticsearch and Kibana &mdash; parsedmarc 10.2.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=b86133f3" />
<link rel="stylesheet" type="text/css" href="_static/css/theme.css?v=9edc463e" />
<script src="_static/jquery.js?v=5d32c60e"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
<script src="_static/documentation_options.js?v=43731225"></script>
<script src="_static/documentation_options.js?v=2c8206f1"></script>
<script src="_static/doctools.js?v=fd6eb6e6"></script>
<script src="_static/sphinx_highlight.js?v=6ffebe34"></script>
<script src="_static/js/theme.js"></script>
@@ -91,7 +91,10 @@
<p>To set up visual dashboards of DMARC data, install Elasticsearch and Kibana.</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>Elasticsearch and Kibana 6 or later are required</p>
<p>Elasticsearch and Kibana 8 or later are required (parsedmarcs 8.x Python
client also supports Elasticsearch 9). OpenSearch users must use the
<code class="docutils literal notranslate"><span class="pre">[opensearch]</span></code> configuration section instead — the Elasticsearch 8.x client
refuses to connect to non-Elasticsearch clusters.</p>
</div>
<section id="installation">
<h2>Installation<a class="headerlink" href="#installation" title="Link to this heading"></a></h2>
+4 -2
View File
@@ -5,14 +5,14 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Index &mdash; parsedmarc 10.2.2 documentation</title>
<title>Index &mdash; parsedmarc 10.2.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=b86133f3" />
<link rel="stylesheet" type="text/css" href="_static/css/theme.css?v=9edc463e" />
<script src="_static/jquery.js?v=5d32c60e"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
<script src="_static/documentation_options.js?v=43731225"></script>
<script src="_static/documentation_options.js?v=2c8206f1"></script>
<script src="_static/doctools.js?v=fd6eb6e6"></script>
<script src="_static/sphinx_highlight.js?v=6ffebe34"></script>
<script src="_static/js/theme.js"></script>
@@ -174,6 +174,8 @@
<li><a href="api.html#parsedmarc.elastic.ElasticsearchError">ElasticsearchError</a>
</li>
<li><a href="api.html#parsedmarc.email_results">email_results() (in module parsedmarc)</a>
</li>
<li><a href="api.html#parsedmarc.email_results_via_msgraph">email_results_via_msgraph() (in module parsedmarc)</a>
</li>
<li><a href="api.html#parsedmarc.types.EmailAddress">EmailAddress (class in parsedmarc.types)</a>
</li>
+2 -2
View File
@@ -6,14 +6,14 @@
<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>parsedmarc documentation - Open source DMARC report analyzer and visualizer &mdash; parsedmarc 10.2.2 documentation</title>
<title>parsedmarc documentation - Open source DMARC report analyzer and visualizer &mdash; parsedmarc 10.2.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=b86133f3" />
<link rel="stylesheet" type="text/css" href="_static/css/theme.css?v=9edc463e" />
<script src="_static/jquery.js?v=5d32c60e"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
<script src="_static/documentation_options.js?v=43731225"></script>
<script src="_static/documentation_options.js?v=2c8206f1"></script>
<script src="_static/doctools.js?v=fd6eb6e6"></script>
<script src="_static/sphinx_highlight.js?v=6ffebe34"></script>
<script src="_static/js/theme.js"></script>
+2 -2
View File
@@ -6,14 +6,14 @@
<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Installation &mdash; parsedmarc 10.2.2 documentation</title>
<title>Installation &mdash; parsedmarc 10.2.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=b86133f3" />
<link rel="stylesheet" type="text/css" href="_static/css/theme.css?v=9edc463e" />
<script src="_static/jquery.js?v=5d32c60e"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
<script src="_static/documentation_options.js?v=43731225"></script>
<script src="_static/documentation_options.js?v=2c8206f1"></script>
<script src="_static/doctools.js?v=fd6eb6e6"></script>
<script src="_static/sphinx_highlight.js?v=6ffebe34"></script>
<script src="_static/js/theme.js"></script>
+2 -2
View File
@@ -6,14 +6,14 @@
<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Using the Kibana dashboards &mdash; parsedmarc 10.2.2 documentation</title>
<title>Using the Kibana dashboards &mdash; parsedmarc 10.2.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=b86133f3" />
<link rel="stylesheet" type="text/css" href="_static/css/theme.css?v=9edc463e" />
<script src="_static/jquery.js?v=5d32c60e"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
<script src="_static/documentation_options.js?v=43731225"></script>
<script src="_static/documentation_options.js?v=2c8206f1"></script>
<script src="_static/doctools.js?v=fd6eb6e6"></script>
<script src="_static/sphinx_highlight.js?v=6ffebe34"></script>
<script src="_static/js/theme.js"></script>
+2 -2
View File
@@ -6,14 +6,14 @@
<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>What about mailing lists? &mdash; parsedmarc 10.2.2 documentation</title>
<title>What about mailing lists? &mdash; parsedmarc 10.2.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=b86133f3" />
<link rel="stylesheet" type="text/css" href="_static/css/theme.css?v=9edc463e" />
<script src="_static/jquery.js?v=5d32c60e"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
<script src="_static/documentation_options.js?v=43731225"></script>
<script src="_static/documentation_options.js?v=2c8206f1"></script>
<script src="_static/doctools.js?v=fd6eb6e6"></script>
<script src="_static/sphinx_highlight.js?v=6ffebe34"></script>
<script src="_static/js/theme.js"></script>
BIN
View File
Binary file not shown.
+2 -2
View File
@@ -6,14 +6,14 @@
<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>OpenSearch and Grafana &mdash; parsedmarc 10.2.2 documentation</title>
<title>OpenSearch and Grafana &mdash; parsedmarc 10.2.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=b86133f3" />
<link rel="stylesheet" type="text/css" href="_static/css/theme.css?v=9edc463e" />
<script src="_static/jquery.js?v=5d32c60e"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
<script src="_static/documentation_options.js?v=43731225"></script>
<script src="_static/documentation_options.js?v=2c8206f1"></script>
<script src="_static/doctools.js?v=fd6eb6e6"></script>
<script src="_static/sphinx_highlight.js?v=6ffebe34"></script>
<script src="_static/js/theme.js"></script>
+2 -2
View File
@@ -6,14 +6,14 @@
<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sample outputs &mdash; parsedmarc 10.2.2 documentation</title>
<title>Sample outputs &mdash; parsedmarc 10.2.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=b86133f3" />
<link rel="stylesheet" type="text/css" href="_static/css/theme.css?v=9edc463e" />
<script src="_static/jquery.js?v=5d32c60e"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
<script src="_static/documentation_options.js?v=43731225"></script>
<script src="_static/documentation_options.js?v=2c8206f1"></script>
<script src="_static/doctools.js?v=fd6eb6e6"></script>
<script src="_static/sphinx_highlight.js?v=6ffebe34"></script>
<script src="_static/js/theme.js"></script>
+2 -2
View File
@@ -5,14 +5,14 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Python Module Index &mdash; parsedmarc 10.2.2 documentation</title>
<title>Python Module Index &mdash; parsedmarc 10.2.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=b86133f3" />
<link rel="stylesheet" type="text/css" href="_static/css/theme.css?v=9edc463e" />
<script src="_static/jquery.js?v=5d32c60e"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
<script src="_static/documentation_options.js?v=43731225"></script>
<script src="_static/documentation_options.js?v=2c8206f1"></script>
<script src="_static/doctools.js?v=fd6eb6e6"></script>
<script src="_static/sphinx_highlight.js?v=6ffebe34"></script>
<script src="_static/js/theme.js"></script>
+2 -2
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Search &mdash; parsedmarc 10.2.2 documentation</title>
<title>Search &mdash; parsedmarc 10.2.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=b86133f3" />
<link rel="stylesheet" type="text/css" href="_static/css/theme.css?v=9edc463e" />
@@ -13,7 +13,7 @@
<script src="_static/jquery.js?v=5d32c60e"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
<script src="_static/documentation_options.js?v=43731225"></script>
<script src="_static/documentation_options.js?v=2c8206f1"></script>
<script src="_static/doctools.js?v=fd6eb6e6"></script>
<script src="_static/sphinx_highlight.js?v=6ffebe34"></script>
<script src="_static/js/theme.js"></script>
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -6,14 +6,14 @@
<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Splunk &mdash; parsedmarc 10.2.2 documentation</title>
<title>Splunk &mdash; parsedmarc 10.2.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=b86133f3" />
<link rel="stylesheet" type="text/css" href="_static/css/theme.css?v=9edc463e" />
<script src="_static/jquery.js?v=5d32c60e"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
<script src="_static/documentation_options.js?v=43731225"></script>
<script src="_static/documentation_options.js?v=2c8206f1"></script>
<script src="_static/doctools.js?v=fd6eb6e6"></script>
<script src="_static/sphinx_highlight.js?v=6ffebe34"></script>
<script src="_static/js/theme.js"></script>
+230 -10
View File
@@ -6,14 +6,14 @@
<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Using parsedmarc &mdash; parsedmarc 10.2.2 documentation</title>
<title>Using parsedmarc &mdash; parsedmarc 10.2.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=b86133f3" />
<link rel="stylesheet" type="text/css" href="_static/css/theme.css?v=9edc463e" />
<script src="_static/jquery.js?v=5d32c60e"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
<script src="_static/documentation_options.js?v=43731225"></script>
<script src="_static/documentation_options.js?v=2c8206f1"></script>
<script src="_static/doctools.js?v=fd6eb6e6"></script>
<script src="_static/sphinx_highlight.js?v=6ffebe34"></script>
<script src="_static/js/theme.js"></script>
@@ -357,7 +357,46 @@ for all auth methods except UsernamePassword.</p></li>
current user if using the UsernamePassword auth method, but
could be a shared mailbox if the user has access to the mailbox</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">graph_url</span></code> - str: Microsoft Graph URL. Allows for use of National Clouds (ex Azure Gov)
(Default: <a class="reference external" href="https://graph.microsoft.com">https://graph.microsoft.com</a>)</p></li>
(Default: <a class="reference external" href="https://graph.microsoft.com">https://graph.microsoft.com</a>)</p>
<div class="admonition warning">
<p class="admonition-title">Warning</p>
<p>Setting <code class="docutils literal notranslate"><span class="pre">graph_url</span></code> alone is <strong>not</strong> sufficient for a national/sovereign
cloud tenant. It only changes the Microsoft Graph API root; the
Microsoft Entra ID (Azure AD) token endpoint used for authentication is
not currently configurable in parsedmarc or <code class="docutils literal notranslate"><span class="pre">mailsuite</span></code>, and always
defaults to the global <code class="docutils literal notranslate"><span class="pre">https://login.microsoftonline.com</span></code>. A true
national-cloud deployment also needs its own Entra ID endpoint, so
<code class="docutils literal notranslate"><span class="pre">graph_url</span></code> by itself only helps if your tenant is registered in the
global cloud but you specifically need to reach one of these Graph API
roots (per <a class="reference external" href="https://learn.microsoft.com/en-us/graph/deployments">Microsofts national cloud deployment docs</a>):</p>
<table class="docutils align-default">
<thead>
<tr class="row-odd"><th class="head"><p>National cloud</p></th>
<th class="head"><p>Microsoft Graph URL</p></th>
<th class="head"><p>Entra ID endpoint (not configurable here)</p></th>
</tr>
</thead>
<tbody>
<tr class="row-even"><td><p>Global (default)</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">https://graph.microsoft.com</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">https://login.microsoftonline.com</span></code></p></td>
</tr>
<tr class="row-odd"><td><p>US Government L4 (GCC High)</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">https://graph.microsoft.us</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">https://login.microsoftonline.us</span></code></p></td>
</tr>
<tr class="row-even"><td><p>US Government L5 (DoD)</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">https://dod-graph.microsoft.us</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">https://login.microsoftonline.us</span></code></p></td>
</tr>
<tr class="row-odd"><td><p>China, operated by 21Vianet</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">https://microsoftgraph.chinacloudapi.cn</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">https://login.chinacloudapi.cn</span></code></p></td>
</tr>
</tbody>
</table>
</div>
</li>
<li><p><code class="docutils literal notranslate"><span class="pre">token_file</span></code> - str: Path to save the token file
(Default: <code class="docutils literal notranslate"><span class="pre">.token</span></code>)</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">allow_unencrypted_storage</span></code> - bool: Allows the Azure Identity
@@ -365,12 +404,41 @@ module to fall back to unencrypted token cache (Default: <code class="docutils l
Even if enabled, the cache will always try encrypted storage first.</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p><code class="docutils literal notranslate"><span class="pre">token_file</span></code> stores the serialized authentication record; the
underlying MSAL persistent token cache is separately named
<code class="docutils literal notranslate"><span class="pre">parsedmarc</span></code>, not <code class="docutils literal notranslate"><span class="pre">mailsuite</span></code>s own default cache name. This is
deliberate: the Graph mailbox backend used to live directly in
parsedmarc, and moved into the <code class="docutils literal notranslate"><span class="pre">mailsuite</span></code> dependency in parsedmarc
9.11.0. Explicitly keeping the <code class="docutils literal notranslate"><span class="pre">parsedmarc</span></code> cache name means tokens
cached before that move keep working after upgrading — there is
nothing to migrate and no action needed on your part.</p>
</div>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>You must create an app registration in Azure AD and have an
admin grant the Microsoft Graph <code class="docutils literal notranslate"><span class="pre">Mail.ReadWrite</span></code>
(delegated) permission to the app. If you are using
<code class="docutils literal notranslate"><span class="pre">UsernamePassword</span></code> auth and the mailbox is different from the
username, you must grant the app <code class="docutils literal notranslate"><span class="pre">Mail.ReadWrite.Shared</span></code>.</p>
</div>
<table class="docutils align-default">
<thead>
<tr class="row-odd"><th class="head"><p>Auth method</p></th>
<th class="head"><p>Reading (own mailbox)</p></th>
<th class="head"><p>Reading (shared mailbox)</p></th>
</tr>
</thead>
<tbody>
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">UsernamePassword</span></code> / <code class="docutils literal notranslate"><span class="pre">DeviceCode</span></code> (delegated)</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">Mail.ReadWrite</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">Mail.ReadWrite.Shared</span></code></p></td>
</tr>
<tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">ClientSecret</span></code> / <code class="docutils literal notranslate"><span class="pre">Certificate</span></code> / <code class="docutils literal notranslate"><span class="pre">ClientAssertion</span></code> (application)</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">Mail.ReadWrite</span></code> (application), scoped via <code class="docutils literal notranslate"><span class="pre">New-ApplicationAccessPolicy</span></code></p></td>
<td><p>same as own mailbox — app-only access is scoped by policy, not by permission name</p></td>
</tr>
</tbody>
</table>
<div class="admonition tip">
<p class="admonition-title">Tip</p>
<p><strong>Troubleshooting connections.</strong> Run with <code class="docutils literal notranslate"><span class="pre">--verbose</span></code> to log a
@@ -381,7 +449,12 @@ mailbox, Graph URL) before the connection attempt, and with
from Entra ID, which distinguish a local configuration problem from
an Exchange Online-side one), Microsoft Graph SDK requests, and
<code class="docutils literal notranslate"><span class="pre">httpx</span></code> HTTP request lines. Secret values (passwords, client
secrets, certificate passwords) are never written to logs.</p>
secrets, certificate passwords) are never written to logs.
Connection, mailbox fetch, message send, and <code class="docutils literal notranslate"><span class="pre">--watch</span></code> failures
each log a single ERROR line naming the mailbox, tenant, auth
method, and the Graph <code class="docutils literal notranslate"><span class="pre">request-id</span></code>/<code class="docutils literal notranslate"><span class="pre">client-request-id</span></code> when
available — worth quoting verbatim when contacting Microsoft
support.</p>
</div>
<div class="admonition warning">
<p class="admonition-title">Warning</p>
@@ -401,6 +474,145 @@ group and use that as the group id.</p>
<p>The same application permission and mailbox scoping guidance
applies to the <code class="docutils literal notranslate"><span class="pre">Certificate</span></code> and <code class="docutils literal notranslate"><span class="pre">ClientAssertion</span></code> auth methods.</p>
</div>
<p><strong>Sending the summary email via Microsoft Graph.</strong>
When <code class="docutils literal notranslate"><span class="pre">[msgraph]</span></code> is configured and <code class="docutils literal notranslate"><span class="pre">[smtp]</span></code> has a <code class="docutils literal notranslate"><span class="pre">to</span></code> value but no
<code class="docutils literal notranslate"><span class="pre">host</span></code>, the periodic summary email is sent through the same
already-authenticated Graph mailbox connection used for reading
(<code class="docutils literal notranslate"><span class="pre">/users/{mailbox}/sendMail</span></code>), and a copy is saved to Sent Items.
When <code class="docutils literal notranslate"><span class="pre">[smtp]</span> <span class="pre">host</span></code> is set, SMTP is used regardless of whether
<code class="docutils literal notranslate"><span class="pre">[msgraph]</span></code> is also configured — SMTP is always preferred, with no
automatic fallback to Graph on SMTP failure.</p>
<p>Example config combining <code class="docutils literal notranslate"><span class="pre">[msgraph]</span></code> with a <code class="docutils literal notranslate"><span class="pre">[smtp]</span></code> section that
only sets <code class="docutils literal notranslate"><span class="pre">to</span></code>/<code class="docutils literal notranslate"><span class="pre">subject</span></code> (no <code class="docutils literal notranslate"><span class="pre">host</span></code>):</p>
<div class="highlight-ini notranslate"><div class="highlight"><pre><span></span><span class="k">[msgraph]</span>
<span class="na">auth_method</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">Certificate</span>
<span class="na">client_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
<span class="na">tenant_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
<span class="na">mailbox</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">dmarc-reports@example.com</span>
<span class="na">certificate_path</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">/path/to/cert.pem</span>
<span class="k">[smtp]</span>
<span class="na">to</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">admin@example.com</span>
<span class="na">subject</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">DMARC Summary</span>
</pre></div>
</div>
<p>Required Microsoft Graph permissions, in addition to the
reading-related permissions documented above:</p>
<table class="docutils align-default">
<thead>
<tr class="row-odd"><th class="head"><p>Auth method</p></th>
<th class="head"><p>Reading</p></th>
<th class="head"><p>Sending (own mailbox)</p></th>
<th class="head"><p>Sending (shared mailbox)</p></th>
</tr>
</thead>
<tbody>
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">UsernamePassword</span></code> / <code class="docutils literal notranslate"><span class="pre">DeviceCode</span></code> (delegated)</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">Mail.ReadWrite</span></code> (+<code class="docutils literal notranslate"><span class="pre">.Shared</span></code> for shared)</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">Mail.Send</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">Mail.Send.Shared</span></code></p></td>
</tr>
<tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">ClientSecret</span></code> / <code class="docutils literal notranslate"><span class="pre">Certificate</span></code> / <code class="docutils literal notranslate"><span class="pre">ClientAssertion</span></code> (application)</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">Mail.ReadWrite</span></code> (application)</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">Mail.Send</span></code> (application)</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">Mail.Send</span></code> (application), scoped via <code class="docutils literal notranslate"><span class="pre">New-ApplicationAccessPolicy</span></code></p></td>
</tr>
</tbody>
</table>
<div class="admonition warning">
<p class="admonition-title">Warning</p>
<p>Graph-based sending is only confirmed to work with the app-only
auth methods (<code class="docutils literal notranslate"><span class="pre">ClientSecret</span></code>, <code class="docutils literal notranslate"><span class="pre">Certificate</span></code>, <code class="docutils literal notranslate"><span class="pre">ClientAssertion</span></code>).
The delegated auth methods (<code class="docutils literal notranslate"><span class="pre">UsernamePassword</span></code>, <code class="docutils literal notranslate"><span class="pre">DeviceCode</span></code>)
currently request only the <code class="docutils literal notranslate"><span class="pre">Mail.ReadWrite</span></code>(<code class="docutils literal notranslate"><span class="pre">.Shared</span></code>) scope when
authenticating, not <code class="docutils literal notranslate"><span class="pre">Mail.Send</span></code> — so a delegated connections
access token will not carry <code class="docutils literal notranslate"><span class="pre">Mail.Send</span></code> even if an administrator
has granted it, and <code class="docutils literal notranslate"><span class="pre">/sendMail</span></code> calls are expected to fail with an
access-denied error regardless of whats granted in Azure AD. Use
an app-only auth method if you need Graph-based sending.</p>
</div>
<p><strong>Minimal example configs.</strong> Each auth method needs a different
minimum set of keys. These read-only examples omit <code class="docutils literal notranslate"><span class="pre">[smtp]</span></code>; see
above for adding Graph-based sending on top of any of them.</p>
<p><code class="docutils literal notranslate"><span class="pre">UsernamePassword</span></code> (delegated, own mailbox):</p>
<div class="highlight-ini notranslate"><div class="highlight"><pre><span></span><span class="k">[msgraph]</span>
<span class="na">auth_method</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">UsernamePassword</span>
<span class="na">client_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
<span class="na">client_secret</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
<span class="na">user</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">dmarc-reports@example.com</span>
<span class="na">password</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
</pre></div>
</div>
<p><code class="docutils literal notranslate"><span class="pre">DeviceCode</span></code> (delegated, interactive sign-in on first run — <code class="docutils literal notranslate"><span class="pre">user</span></code>
is the account that signs in; <code class="docutils literal notranslate"><span class="pre">mailbox</span></code> is the shared mailbox it
reads, and only needs to differ from <code class="docutils literal notranslate"><span class="pre">user</span></code> to request
<code class="docutils literal notranslate"><span class="pre">Mail.ReadWrite.Shared</span></code> instead of plain <code class="docutils literal notranslate"><span class="pre">Mail.ReadWrite</span></code>):</p>
<div class="highlight-ini notranslate"><div class="highlight"><pre><span></span><span class="k">[msgraph]</span>
<span class="na">auth_method</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">DeviceCode</span>
<span class="na">client_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
<span class="na">tenant_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
<span class="na">user</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">signing-in-user@example.com</span>
<span class="na">mailbox</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">dmarc-reports@example.com</span>
</pre></div>
</div>
<p><code class="docutils literal notranslate"><span class="pre">ClientSecret</span></code> (app-only):</p>
<div class="highlight-ini notranslate"><div class="highlight"><pre><span></span><span class="k">[msgraph]</span>
<span class="na">auth_method</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">ClientSecret</span>
<span class="na">client_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
<span class="na">tenant_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
<span class="na">client_secret</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
<span class="na">mailbox</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">dmarc-reports@example.com</span>
</pre></div>
</div>
<p><code class="docutils literal notranslate"><span class="pre">Certificate</span></code> (app-only):</p>
<div class="highlight-ini notranslate"><div class="highlight"><pre><span></span><span class="k">[msgraph]</span>
<span class="na">auth_method</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">Certificate</span>
<span class="na">client_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
<span class="na">tenant_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
<span class="na">certificate_path</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">/path/to/cert.pem</span>
<span class="na">mailbox</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">dmarc-reports@example.com</span>
</pre></div>
</div>
<p><code class="docutils literal notranslate"><span class="pre">ClientAssertion</span></code> (app-only, short-lived JWT — see the note above
about its unsuitability for <code class="docutils literal notranslate"><span class="pre">watch</span></code> mode):</p>
<div class="highlight-ini notranslate"><div class="highlight"><pre><span></span><span class="k">[msgraph]</span>
<span class="na">auth_method</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">ClientAssertion</span>
<span class="na">client_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
<span class="na">tenant_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
<span class="na">client_assertion</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
<span class="na">mailbox</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">dmarc-reports@example.com</span>
</pre></div>
</div>
<div class="admonition tip">
<p class="admonition-title">Tip</p>
<p><strong>Troubleshooting.</strong></p>
<table class="docutils align-default">
<thead>
<tr class="row-odd"><th class="head"><p>Error</p></th>
<th class="head"><p>Cause</p></th>
<th class="head"><p>Fix</p></th>
</tr>
</thead>
<tbody>
<tr class="row-even"><td><p><em>“…needs permission to access resources in your organization that only an admin can grant”</em> / “Admin consent required”</p></td>
<td><p>A delegated auth method (<code class="docutils literal notranslate"><span class="pre">UsernamePassword</span></code>, <code class="docutils literal notranslate"><span class="pre">DeviceCode</span></code>) is authenticating with a scope (<code class="docutils literal notranslate"><span class="pre">Mail.ReadWrite</span></code> or <code class="docutils literal notranslate"><span class="pre">Mail.ReadWrite.Shared</span></code>) the tenant admin hasnt consented to yet.</p></td>
<td><p>Have an Entra ID admin grant consent: Azure Portal → <strong>Enterprise Applications</strong><em>your app</em><strong>Permissions</strong><strong>Grant admin consent</strong>, or <code class="docutils literal notranslate"><span class="pre">az</span> <span class="pre">ad</span> <span class="pre">app</span> <span class="pre">permission</span> <span class="pre">admin-consent</span> <span class="pre">--id</span> <span class="pre">&lt;CLIENT_ID&gt;</span></code>. This is separate from the <code class="docutils literal notranslate"><span class="pre">New-ApplicationAccessPolicy</span></code> step above, which only applies to app-only auth.</p></td>
</tr>
<tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">ErrorItemNotFound:</span> <span class="pre">...</span> <span class="pre">Default</span> <span class="pre">folder</span> <span class="pre">Root</span> <span class="pre">not</span> <span class="pre">found</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">mailsuite</span></code> can resolve the well-known folders (<code class="docutils literal notranslate"><span class="pre">Inbox</span></code>, <code class="docutils literal notranslate"><span class="pre">Archive</span></code>, <code class="docutils literal notranslate"><span class="pre">Drafts</span></code>, <code class="docutils literal notranslate"><span class="pre">Sent</span> <span class="pre">Items</span></code>, <code class="docutils literal notranslate"><span class="pre">Deleted</span> <span class="pre">Items</span></code>, <code class="docutils literal notranslate"><span class="pre">Junk</span> <span class="pre">Email</span></code>) even when a mailboxs folder hierarchy hasnt fully provisioned, but a <strong>custom, non-well-known</strong> <code class="docutils literal notranslate"><span class="pre">reports_folder</span></code> name still fails to resolve on such a mailbox.</p></td>
<td><p>Point <code class="docutils literal notranslate"><span class="pre">reports_folder</span></code> at (or under) one of the six well-known folder names above, or sign into the (shared) mailbox once via Outlook/OWA to force Exchange to provision it, then retry.</p></td>
</tr>
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">RuntimeError:</span> <span class="pre">Event</span> <span class="pre">loop</span> <span class="pre">is</span> <span class="pre">closed</span></code></p></td>
<td><p>Historical bug, fixed in <code class="docutils literal notranslate"><span class="pre">mailsuite</span></code> 2.0.2. Not reachable with the <code class="docutils literal notranslate"><span class="pre">mailsuite&gt;=2.2.2</span></code> this project requires.</p></td>
<td><p>Confirm your installed <code class="docutils literal notranslate"><span class="pre">mailsuite</span></code> version is current (<code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">show</span> <span class="pre">mailsuite</span></code>); upgrade if its somehow pinned below 2.0.2.</p></td>
</tr>
<tr class="row-odd"><td><p>Invalid/rejected timestamp in the <code class="docutils literal notranslate"><span class="pre">since</span></code>/<code class="docutils literal notranslate"><span class="pre">receivedDateTime</span></code> filter</p></td>
<td><p>Historical bug (parsedmarc <a class="reference external" href="https://github.com/domainaware/parsedmarc/pull/706">#706</a>/<a class="reference external" href="https://github.com/domainaware/parsedmarc/pull/708">#708</a>): older versions appended a spurious <code class="docutils literal notranslate"><span class="pre">Z</span></code> to an already-UTC-offset ISO timestamp. Fixed since parsedmarc 9.5.1/9.5.5.</p></td>
<td><p>Upgrade parsedmarc if youre on a version older than 9.5.5.</p></td>
</tr>
</tbody>
</table>
</div>
</li>
</ul>
</li>
@@ -495,20 +707,28 @@ verification (not recommended)</p></li>
</li>
<li><p><code class="docutils literal notranslate"><span class="pre">smtp</span></code></p>
<ul>
<li><p><code class="docutils literal notranslate"><span class="pre">host</span></code> - str: The SMTP hostname</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">host</span></code> - str: The SMTP hostname. Required unless <code class="docutils literal notranslate"><span class="pre">[msgraph]</span></code> is
configured, in which case omitting it sends the summary via
Microsoft Graph instead — see “Sending the summary email via
Microsoft Graph” above.</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">port</span></code> - int: The SMTP port (Default: <code class="docutils literal notranslate"><span class="pre">25</span></code>)</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">ssl</span></code> - bool: Require SSL/TLS instead of using STARTTLS</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">skip_certificate_verification</span></code> - bool: Skip certificate
verification (not recommended)</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">user</span></code> - str: the SMTP username</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">password</span></code> - str: the SMTP password</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">from</span></code> - str: The From header to use in the email</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">user</span></code> - str: the SMTP username. SMTP-only; not used when sending
via Microsoft Graph.</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">password</span></code> - str: the SMTP password. SMTP-only; not used when
sending via Microsoft Graph.</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">from</span></code> - str: The From header to use in the email. SMTP-only.
When sent via Microsoft Graph, the messages <code class="docutils literal notranslate"><span class="pre">From</span></code> is always the
<code class="docutils literal notranslate"><span class="pre">[msgraph]</span></code> mailbox — <code class="docutils literal notranslate"><span class="pre">[smtp]</span> <span class="pre">from</span></code> has no effect.</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">to</span></code> - list: A list of email addresses to send to</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">subject</span></code> - str: The Subject header to use in the email
(Default: <code class="docutils literal notranslate"><span class="pre">parsedmarc</span> <span class="pre">report</span></code>)</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">attachment</span></code> - str: The ZIP attachment filenames</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">attachment</span></code> - str: The ZIP attachment filenames
(Default: <code class="docutils literal notranslate"><span class="pre">DMARC-&lt;YYYY-MM-DD&gt;.zip</span></code>)</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">message</span></code> - str: The email message
(Default: <code class="docutils literal notranslate"><span class="pre">Please</span> <span class="pre">see</span> <span class="pre">the</span> <span class="pre">attached</span> <span class="pre">parsedmarc</span> <span class="pre">report.</span></code>)</p>
(Default: <code class="docutils literal notranslate"><span class="pre">Please</span> <span class="pre">see</span> <span class="pre">the</span> <span class="pre">attached</span> <span class="pre">DMARC</span> <span class="pre">results.</span></code>)</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p><code class="docutils literal notranslate"><span class="pre">%</span></code> characters must be escaped with another <code class="docutils literal notranslate"><span class="pre">%</span></code> character,