webadmin: in list with dmarc evaluations, add the dispositions applied

to easily spot rejects
This commit is contained in:
Mechiel Lukkien 2023-11-13 14:44:40 +01:00
parent bcb80c3598
commit 651fa68067
No known key found for this signature in database
4 changed files with 41 additions and 22 deletions

View file

@ -22,6 +22,7 @@ import (
"time"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
@ -219,9 +220,10 @@ func Evaluations(ctx context.Context) ([]Evaluation, error) {
// EvaluationStat summarizes stored evaluations, for inclusion in an upcoming
// aggregate report, for a domain.
type EvaluationStat struct {
Domain dns.Domain
Dispositions []string
Count int
SendReport bool
Domain dns.Domain
}
// EvaluationStats returns evaluation counts and report-sending status per domain.
@ -235,6 +237,9 @@ func EvaluationStats(ctx context.Context) (map[string]EvaluationStat, error) {
err = bstore.QueryDB[Evaluation](ctx, db).ForEach(func(e Evaluation) error {
if stat, ok := r[e.PolicyDomain]; ok {
if !slices.Contains(stat.Dispositions, string(e.Disposition)) {
stat.Dispositions = append(stat.Dispositions, string(e.Disposition))
}
stat.Count++
stat.SendReport = stat.SendReport || !e.Optional
r[e.PolicyDomain] = stat
@ -244,9 +249,10 @@ func EvaluationStats(ctx context.Context) (map[string]EvaluationStat, error) {
return fmt.Errorf("parsing domain %q: %v", e.PolicyDomain, err)
}
r[e.PolicyDomain] = EvaluationStat{
Domain: dom,
Dispositions: []string{string(e.Disposition)},
Count: 1,
SendReport: !e.Optional,
Domain: dom,
}
}
return nil

View file

@ -109,14 +109,16 @@ func TestEvaluations(t *testing.T) {
expStats := map[string]EvaluationStat{
"sender1.example": {
Domain: dns.Domain{ASCII: "sender1.example"},
Dispositions: []string{"none"},
Count: 3,
SendReport: true,
Domain: dns.Domain{ASCII: "sender1.example"},
},
"sender2.example": {
Domain: dns.Domain{ASCII: "sender2.example"},
Dispositions: []string{"none"},
Count: 1,
SendReport: true,
Domain: dns.Domain{ASCII: "sender2.example"},
},
}
stats, err := EvaluationStats(ctxbg)
@ -142,9 +144,10 @@ func TestEvaluations(t *testing.T) {
expStats = map[string]EvaluationStat{
"sender2.example": {
Domain: dns.Domain{ASCII: "sender2.example"},
Dispositions: []string{"none"},
Count: 1,
SendReport: true,
Domain: dns.Domain{ASCII: "sender2.example"},
},
}
stats, err = EvaluationStats(ctxbg)

View file

@ -1112,6 +1112,7 @@ const dmarcEvaluations = async () => {
dom.thead(
dom.tr(
dom.th('Domain', attr({title: 'Domain in the message From header. Keep in mind these can be forged, so this does not necessarily mean someone from this domain authentically tried delivering email.'})),
dom.th('Dispositions', attr({title: 'Unique dispositions occurring in report.'})),
dom.th('Evaluations', attr({title: 'Total number of message delivery attempts, including retries.'})),
dom.th('Send report', attr({title: 'Whether the current evaluations will cause a report to be sent.'})),
),
@ -1120,6 +1121,7 @@ const dmarcEvaluations = async () => {
Object.entries(evalStats).sort((a, b) => a[0] < b[0] ? -1 : 1).map(t =>
dom.tr(
dom.td(dom.a(attr({href: '#dmarc/evaluations/'+domainName(t[1].Domain)}), domainString(t[1].Domain))),
dom.td((t[1].Dispositions || []).join(' ')),
dom.td(style({textAlign: 'right'}), ''+t[1].Count),
dom.td(style({textAlign: 'right'}), t[1].SendReport ? '✓' : ''),
),

View file

@ -3798,6 +3798,21 @@
"Name": "EvaluationStat",
"Docs": "EvaluationStat summarizes stored evaluations, for inclusion in an upcoming\naggregate report, for a domain.",
"Fields": [
{
"Name": "Domain",
"Docs": "",
"Typewords": [
"Domain"
]
},
{
"Name": "Dispositions",
"Docs": "",
"Typewords": [
"[]",
"string"
]
},
{
"Name": "Count",
"Docs": "",
@ -3811,13 +3826,6 @@
"Typewords": [
"bool"
]
},
{
"Name": "Domain",
"Docs": "",
"Typewords": [
"Domain"
]
}
]
},