Skip to content

Commit

Permalink
feat: DMARC Report
Browse files Browse the repository at this point in the history
  • Loading branch information
s-aga-r committed Nov 20, 2024
1 parent 83ef9cd commit 27cec4f
Show file tree
Hide file tree
Showing 9 changed files with 369 additions and 1 deletion.
Empty file.
8 changes: 8 additions & 0 deletions mail_client/mail_client/doctype/dmarc_report/dmarc_report.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt

// frappe.ui.form.on("DMARC Report", {
// refresh(frm) {

// },
// });
138 changes: 138 additions & 0 deletions mail_client/mail_client/doctype/dmarc_report/dmarc_report.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
{
"actions": [],
"creation": "2024-11-19 11:38:22.846485",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"section_break_nb25",
"domain_name",
"policy_published",
"column_break_kyq2",
"organization",
"email",
"extra_contact_info",
"section_break_bgcw",
"from_date",
"column_break_lztn",
"to_date",
"section_break_prwz",
"records"
],
"fields": [
{
"fieldname": "section_break_nb25",
"fieldtype": "Section Break"
},
{
"fieldname": "organization",
"fieldtype": "Data",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Organization",
"no_copy": 1,
"read_only": 1
},
{
"fieldname": "email",
"fieldtype": "Data",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Email",
"no_copy": 1,
"read_only": 1
},
{
"fieldname": "from_date",
"fieldtype": "Datetime",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "From Date",
"no_copy": 1,
"read_only": 1
},
{
"fieldname": "to_date",
"fieldtype": "Datetime",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "To Date",
"no_copy": 1,
"read_only": 1
},
{
"fieldname": "domain_name",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Domain Name",
"no_copy": 1,
"options": "Mail Domain",
"read_only": 1,
"search_index": 1
},
{
"fieldname": "column_break_kyq2",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_bgcw",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_lztn",
"fieldtype": "Column Break"
},
{
"fieldname": "extra_contact_info",
"fieldtype": "Small Text",
"label": "Extra Contact Info",
"no_copy": 1,
"read_only": 1
},
{
"fieldname": "policy_published",
"fieldtype": "Small Text",
"label": "Policy Published",
"no_copy": 1,
"read_only": 1
},
{
"fieldname": "section_break_prwz",
"fieldtype": "Section Break"
},
{
"fieldname": "records",
"fieldtype": "Table",
"label": "Records",
"no_copy": 1,
"options": "DMARC Report Detail",
"read_only": 1
}
],
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-11-19 20:02:12.652268",
"modified_by": "Administrator",
"module": "Mail Client",
"name": "DMARC Report",
"naming_rule": "Set by user",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
105 changes: 105 additions & 0 deletions mail_client/mail_client/doctype/dmarc_report/dmarc_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt

import json
from datetime import datetime, timezone

import frappe
import xmltodict
from frappe.model.document import Document
from frappe.utils import cint, convert_utc_to_system_timezone, get_datetime_str
from uuid_utils import uuid7


class DMARCReport(Document):
def autoname(self) -> None:
self.name = self.report_id or str(uuid7())


def create_dmarc_report(xml_content: str) -> "DMARCReport":
"""Create a DMARC Report from the given XML content."""

root = xmltodict.parse(xml_content)
feedback = root["feedback"]

report_metadata = feedback["report_metadata"]
policy_published = feedback["policy_published"]
records = feedback["record"]

doc = frappe.new_doc("DMARC Report")
doc.organization = report_metadata["org_name"]
doc.email = report_metadata["email"]
doc.report_id = report_metadata["report_id"]
doc.extra_contact_info = report_metadata.get("extra_contact_info", "") # Optional

date_range = report_metadata["date_range"]
doc.from_date = get_datetime_str(
convert_utc_to_system_timezone(datetime.fromtimestamp(int(date_range["begin"]), tz=timezone.utc))
)
doc.to_date = get_datetime_str(
convert_utc_to_system_timezone(datetime.fromtimestamp(int(date_range["end"]), tz=timezone.utc))
)

doc.domain_name = policy_published["domain"]
doc.policy_published = json.dumps(
{
"adkim": policy_published["adkim"],
"aspf": policy_published["aspf"],
"p": policy_published["p"],
"sp": policy_published.get("sp", ""), # Optional
"pct": policy_published.get("pct", ""), # Optional
"np": policy_published.get("np", ""), # Optional
"fo": policy_published.get("fo", ""), # Optional
},
indent=4,
)

if isinstance(records, dict):
records = [records]

for record in records:
row = record["row"]
policy_evaluated = row["policy_evaluated"]
identifiers = record["identifiers"]
auth_results = record["auth_results"]

source_ip = row["source_ip"]
count = row["count"]
disposition = policy_evaluated["disposition"]
dkim_result = policy_evaluated["dkim"]
spf_result = policy_evaluated["spf"]
header_from = identifiers["header_from"]

results = []
for auth_type, auth_result in auth_results.items():
if isinstance(auth_result, dict):
auth_result = [auth_result]

for result in auth_result:
result["auth_type"] = auth_type
results.append(result)

doc.append(
"records",
{
"source_ip": source_ip,
"count": cint(count),
"disposition": disposition,
"dkim_result": dkim_result,
"spf_result": spf_result,
"header_from": header_from,
"auth_results": json.dumps(results, indent=4),
},
)

doc.flags.ignore_links = True

try:
doc.insert(ignore_permissions=True)
return doc
except frappe.DuplicateEntryError:
frappe.log_error(
title="Duplicate DMARC Report",
message=frappe.get_traceback(with_context=True),
)
return frappe.get_doc("DMARC Report", doc.name)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt

# import frappe
from frappe.tests.utils import FrappeTestCase


class TestDMARCReport(FrappeTestCase):
pass
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2024-11-19 12:51:48.671062",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"section_break_nmh9",
"source_ip",
"column_break_lsuy",
"count",
"section_break_bqnt",
"disposition",
"header_from",
"column_break_q04b",
"dkim_result",
"spf_result",
"section_break_0qf4",
"auth_results"
],
"fields": [
{
"fieldname": "source_ip",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Source IP"
},
{
"fieldname": "count",
"fieldtype": "Int",
"in_list_view": 1,
"label": "Count"
},
{
"fieldname": "disposition",
"fieldtype": "Data",
"label": "Disposition"
},
{
"fieldname": "dkim_result",
"fieldtype": "Select",
"in_list_view": 1,
"label": "DKIM Result",
"options": "\npass\nfail"
},
{
"fieldname": "spf_result",
"fieldtype": "Select",
"in_list_view": 1,
"label": "SPF Result",
"options": "\npass\nfail"
},
{
"fieldname": "header_from",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Header From"
},
{
"fieldname": "auth_results",
"fieldtype": "JSON",
"label": "Auth Results"
},
{
"fieldname": "section_break_nmh9",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_lsuy",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_bqnt",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_q04b",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_0qf4",
"fieldtype": "Section Break"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-11-19 13:34:47.458647",
"modified_by": "Administrator",
"module": "Mail Client",
"name": "DMARC Report Detail",
"owner": "Administrator",
"permissions": [],
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt

# import frappe
from frappe.model.document import Document


class DMARCReportDetail(Document):
pass
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ readme = "README.md"
dynamic = ["version"]
dependencies = [
# "frappe~=15.0.0" # Installed and managed by bench.
"uuid-utils~=0.6.1",
"dkimpy~=1.1.5",
"uuid-utils~=0.6.1",
"xmltodict~=0.14.2",
]

[build-system]
Expand Down

0 comments on commit 27cec4f

Please sign in to comment.