288 lines
13 KiB
Ruby
288 lines
13 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "email/authentication_results"
|
|
|
|
RSpec.describe Email::AuthenticationResults do
|
|
describe "#results" do
|
|
it "parses 'Nearly Trivial Case: Service Provided, but No Authentication Done' correctly" do
|
|
# https://tools.ietf.org/html/rfc8601#appendix-B.2
|
|
results = described_class.new(" example.org 1; none").results
|
|
expect(results[0][:authserv_id]).to eq "example.org"
|
|
expect(results[0][:resinfo]).to be nil
|
|
end
|
|
|
|
it "parses 'Service Provided, Authentication Done' correctly" do
|
|
# https://tools.ietf.org/html/rfc8601#appendix-B.3
|
|
results = described_class.new(<<~RAW).results
|
|
example.com;
|
|
spf=pass smtp.mailfrom=example.net
|
|
RAW
|
|
expect(results[0][:authserv_id]).to eq "example.com"
|
|
expect(results[0][:resinfo][0][:method]).to eq "spf"
|
|
expect(results[0][:resinfo][0][:result]).to eq "pass"
|
|
expect(results[0][:resinfo][0][:reason]).to be nil
|
|
expect(results[0][:resinfo][0][:props][0][:ptype]).to eq "smtp"
|
|
expect(results[0][:resinfo][0][:props][0][:property]).to eq "mailfrom"
|
|
expect(results[0][:resinfo][0][:props][0][:pvalue]).to eq "example.net"
|
|
end
|
|
|
|
it "parses 'Service Provided, Several Authentications Done, Single MTA' correctly" do
|
|
# https://tools.ietf.org/html/rfc8601#appendix-B.4
|
|
results = described_class.new([<<~RAW, <<~RAW]).results
|
|
example.com;
|
|
auth=pass (cram-md5) smtp.auth=sender@example.net;
|
|
spf=pass smtp.mailfrom=example.net
|
|
RAW
|
|
example.com; iprev=pass
|
|
policy.iprev=192.0.2.200
|
|
RAW
|
|
|
|
expect(results[0][:authserv_id]).to eq "example.com"
|
|
expect(results[0][:resinfo][0][:method]).to eq "auth"
|
|
expect(results[0][:resinfo][0][:result]).to eq "pass"
|
|
expect(results[0][:resinfo][0][:reason]).to be nil
|
|
expect(results[0][:resinfo][0][:props][0][:ptype]).to eq "smtp"
|
|
expect(results[0][:resinfo][0][:props][0][:property]).to eq "auth"
|
|
expect(results[0][:resinfo][0][:props][0][:pvalue]).to eq "sender@example.net"
|
|
expect(results[0][:resinfo][1][:method]).to eq "spf"
|
|
expect(results[0][:resinfo][1][:result]).to eq "pass"
|
|
expect(results[0][:resinfo][1][:reason]).to be nil
|
|
expect(results[0][:resinfo][1][:props][0][:ptype]).to eq "smtp"
|
|
expect(results[0][:resinfo][1][:props][0][:property]).to eq "mailfrom"
|
|
expect(results[0][:resinfo][1][:props][0][:pvalue]).to eq "example.net"
|
|
expect(results[1][:authserv_id]).to eq "example.com"
|
|
expect(results[1][:resinfo][0][:method]).to eq "iprev"
|
|
expect(results[1][:resinfo][0][:result]).to eq "pass"
|
|
expect(results[1][:resinfo][0][:reason]).to be nil
|
|
expect(results[1][:resinfo][0][:props][0][:ptype]).to eq "policy"
|
|
expect(results[1][:resinfo][0][:props][0][:property]).to eq "iprev"
|
|
expect(results[1][:resinfo][0][:props][0][:pvalue]).to eq "192.0.2.200"
|
|
end
|
|
|
|
it "parses 'Service Provided, Several Authentications Done, Different MTAs' correctly" do
|
|
# https://tools.ietf.org/html/rfc8601#appendix-B.5
|
|
results = described_class.new([<<~RAW, <<~RAW]).results
|
|
example.com;
|
|
dkim=pass (good signature) header.d=example.com
|
|
RAW
|
|
example.com;
|
|
auth=pass (cram-md5) smtp.auth=sender@example.com;
|
|
spf=fail smtp.mailfrom=example.com
|
|
RAW
|
|
|
|
expect(results[0][:authserv_id]).to eq "example.com"
|
|
expect(results[0][:resinfo][0][:method]).to eq "dkim"
|
|
expect(results[0][:resinfo][0][:result]).to eq "pass"
|
|
expect(results[0][:resinfo][0][:reason]).to be nil
|
|
expect(results[0][:resinfo][0][:props][0][:ptype]).to eq "header"
|
|
expect(results[0][:resinfo][0][:props][0][:property]).to eq "d"
|
|
expect(results[0][:resinfo][0][:props][0][:pvalue]).to eq "example.com"
|
|
expect(results[1][:authserv_id]).to eq "example.com"
|
|
expect(results[1][:resinfo][0][:method]).to eq "auth"
|
|
expect(results[1][:resinfo][0][:result]).to eq "pass"
|
|
expect(results[1][:resinfo][0][:reason]).to be nil
|
|
expect(results[1][:resinfo][0][:props][0][:ptype]).to eq "smtp"
|
|
expect(results[1][:resinfo][0][:props][0][:property]).to eq "auth"
|
|
expect(results[1][:resinfo][0][:props][0][:pvalue]).to eq "sender@example.com"
|
|
expect(results[1][:resinfo][1][:method]).to eq "spf"
|
|
expect(results[1][:resinfo][1][:result]).to eq "fail"
|
|
expect(results[1][:resinfo][1][:reason]).to be nil
|
|
expect(results[1][:resinfo][1][:props][0][:ptype]).to eq "smtp"
|
|
expect(results[1][:resinfo][1][:props][0][:property]).to eq "mailfrom"
|
|
expect(results[1][:resinfo][1][:props][0][:pvalue]).to eq "example.com"
|
|
end
|
|
|
|
it "parses 'Service Provided, Multi-tiered Authentication Done' correctly" do
|
|
# https://tools.ietf.org/html/rfc8601#appendix-B.6
|
|
results = described_class.new([<<~RAW, <<~RAW]).results
|
|
example.com;
|
|
dkim=pass reason="good signature"
|
|
header.i=@mail-router.example.net;
|
|
dkim=fail reason="bad signature"
|
|
header.i=@newyork.example.com
|
|
RAW
|
|
example.net;
|
|
dkim=pass (good signature) header.i=@newyork.example.com
|
|
RAW
|
|
|
|
expect(results[0][:authserv_id]).to eq "example.com"
|
|
expect(results[0][:resinfo][0][:method]).to eq "dkim"
|
|
expect(results[0][:resinfo][0][:result]).to eq "pass"
|
|
expect(results[0][:resinfo][0][:reason]).to eq "good signature"
|
|
expect(results[0][:resinfo][0][:props][0][:ptype]).to eq "header"
|
|
expect(results[0][:resinfo][0][:props][0][:property]).to eq "i"
|
|
expect(results[0][:resinfo][0][:props][0][:pvalue]).to eq "@mail-router.example.net"
|
|
expect(results[0][:resinfo][1][:method]).to eq "dkim"
|
|
expect(results[0][:resinfo][1][:result]).to eq "fail"
|
|
expect(results[0][:resinfo][1][:reason]).to eq "bad signature"
|
|
expect(results[0][:resinfo][1][:props][0][:ptype]).to eq "header"
|
|
expect(results[0][:resinfo][1][:props][0][:property]).to eq "i"
|
|
expect(results[0][:resinfo][1][:props][0][:pvalue]).to eq "@newyork.example.com"
|
|
expect(results[1][:authserv_id]).to eq "example.net"
|
|
expect(results[1][:resinfo][0][:method]).to eq "dkim"
|
|
expect(results[1][:resinfo][0][:result]).to eq "pass"
|
|
expect(results[1][:resinfo][0][:reason]).to be nil
|
|
expect(results[1][:resinfo][0][:props][0][:ptype]).to eq "header"
|
|
expect(results[1][:resinfo][0][:props][0][:property]).to eq "i"
|
|
expect(results[1][:resinfo][0][:props][0][:pvalue]).to eq "@newyork.example.com"
|
|
end
|
|
|
|
it "parses 'Comment-Heavy Example' correctly" do
|
|
# https://tools.ietf.org/html/rfc8601#appendix-B.7
|
|
results = described_class.new(<<~RAW).results
|
|
foo.example.net (foobar) 1 (baz);
|
|
dkim (Because I like it) / 1 (One yay) = (wait for it) fail
|
|
policy (A dot can go here) . (like that) expired
|
|
(this surprised me) = (as I wasn't expecting it) 1362471462
|
|
RAW
|
|
|
|
expect(results[0][:authserv_id]).to eq "foo.example.net"
|
|
expect(results[0][:resinfo][0][:method]).to eq "dkim"
|
|
expect(results[0][:resinfo][0][:result]).to eq "fail"
|
|
expect(results[0][:resinfo][0][:reason]).to be nil
|
|
expect(results[0][:resinfo][0][:props][0][:ptype]).to eq "policy"
|
|
expect(results[0][:resinfo][0][:props][0][:property]).to eq "expired"
|
|
expect(results[0][:resinfo][0][:props][0][:pvalue]).to eq "1362471462"
|
|
end
|
|
|
|
it "parses header with no props correctly" do
|
|
results = described_class.new(" example.com; dmarc=pass").results
|
|
expect(results[0][:authserv_id]).to eq "example.com"
|
|
expect(results[0][:resinfo][0][:method]).to eq "dmarc"
|
|
expect(results[0][:resinfo][0][:result]).to eq "pass"
|
|
expect(results[0][:resinfo][0][:reason]).to be nil
|
|
expect(results[0][:resinfo][0][:props]).to eq []
|
|
end
|
|
|
|
it "parses header with multiple props correctly" do
|
|
results = described_class.new(<<~RAW).results
|
|
mx.google.com;
|
|
dkim=pass header.i=@email.example.com header.s=20111006 header.b=URn9MW+F;
|
|
spf=pass (google.com: domain of foo@b.email.example.com designates 1.2.3.4 as permitted sender) smtp.mailfrom=foo@b.email.example.com;
|
|
dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=email.example.com
|
|
RAW
|
|
|
|
expect(results[0][:authserv_id]).to eq "mx.google.com"
|
|
expect(results[0][:resinfo][0][:method]).to eq "dkim"
|
|
expect(results[0][:resinfo][0][:result]).to eq "pass"
|
|
expect(results[0][:resinfo][0][:reason]).to be nil
|
|
expect(results[0][:resinfo][0][:props][0][:ptype]).to eq "header"
|
|
expect(results[0][:resinfo][0][:props][0][:property]).to eq "i"
|
|
expect(results[0][:resinfo][0][:props][0][:pvalue]).to eq "@email.example.com"
|
|
expect(results[0][:resinfo][0][:props][1][:ptype]).to eq "header"
|
|
expect(results[0][:resinfo][0][:props][1][:property]).to eq "s"
|
|
expect(results[0][:resinfo][0][:props][1][:pvalue]).to eq "20111006"
|
|
expect(results[0][:resinfo][0][:props][2][:ptype]).to eq "header"
|
|
expect(results[0][:resinfo][0][:props][2][:property]).to eq "b"
|
|
expect(results[0][:resinfo][0][:props][2][:pvalue]).to eq "URn9MW+F"
|
|
expect(results[0][:resinfo][1][:method]).to eq "spf"
|
|
expect(results[0][:resinfo][1][:result]).to eq "pass"
|
|
expect(results[0][:resinfo][1][:reason]).to be nil
|
|
expect(results[0][:resinfo][1][:props][0][:ptype]).to eq "smtp"
|
|
expect(results[0][:resinfo][1][:props][0][:property]).to eq "mailfrom"
|
|
expect(results[0][:resinfo][1][:props][0][:pvalue]).to eq "foo@b.email.example.com"
|
|
expect(results[0][:resinfo][2][:method]).to eq "dmarc"
|
|
expect(results[0][:resinfo][2][:result]).to eq "pass"
|
|
expect(results[0][:resinfo][2][:reason]).to be nil
|
|
expect(results[0][:resinfo][2][:props][0][:ptype]).to eq "header"
|
|
expect(results[0][:resinfo][2][:props][0][:property]).to eq "from"
|
|
expect(results[0][:resinfo][2][:props][0][:pvalue]).to eq "email.example.com"
|
|
end
|
|
end
|
|
|
|
describe "#verdict" do
|
|
before { SiteSetting.email_in_authserv_id = "valid.com" }
|
|
|
|
shared_examples "is verdict" do |verdict|
|
|
it "is #{verdict}" do
|
|
expect(described_class.new(headers).verdict).to eq verdict
|
|
end
|
|
end
|
|
|
|
context "with no authentication-results headers" do
|
|
let(:headers) { "" }
|
|
|
|
it "is gray" do
|
|
expect(described_class.new(headers).verdict).to eq :gray
|
|
end
|
|
end
|
|
|
|
context "with a single authentication-results header" do
|
|
context "with a valid fail" do
|
|
let(:headers) { "valid.com; dmarc=fail" }
|
|
include_examples "is verdict", :fail
|
|
end
|
|
|
|
context "with a valid pass" do
|
|
let(:headers) { "valid.com; dmarc=pass" }
|
|
include_examples "is verdict", :pass
|
|
end
|
|
|
|
context "with a valid error" do
|
|
let(:headers) { "valid.com; dmarc=error" }
|
|
include_examples "is verdict", :gray
|
|
end
|
|
|
|
context "with no email_in_authserv_id set" do
|
|
before { SiteSetting.email_in_authserv_id = "" }
|
|
|
|
context "with a fail" do
|
|
let(:headers) { "foobar.com; dmarc=fail" }
|
|
include_examples "is verdict", :gray
|
|
end
|
|
|
|
context "with a pass" do
|
|
let(:headers) { "foobar.com; dmarc=pass" }
|
|
include_examples "is verdict", :gray
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with multiple authentication-results headers" do
|
|
context "with a valid fail, and an invalid pass" do
|
|
let(:headers) { ["valid.com; dmarc=fail", "invalid.com; dmarc=pass"] }
|
|
include_examples "is verdict", :fail
|
|
end
|
|
|
|
context "with a valid fail, and a valid pass" do
|
|
let(:headers) { ["valid.com; dmarc=fail", "valid.com; dmarc=pass"] }
|
|
include_examples "is verdict", :fail
|
|
end
|
|
|
|
context "with a valid error, and a valid pass" do
|
|
let(:headers) { ["valid.com; dmarc=foobar", "valid.com; dmarc=pass"] }
|
|
include_examples "is verdict", :pass
|
|
end
|
|
|
|
context "with no email_in_authserv_id set" do
|
|
before { SiteSetting.email_in_authserv_id = "" }
|
|
|
|
context "with an error, and a pass" do
|
|
let(:headers) { ["foobar.com; dmarc=foobar", "foobar.com; dmarc=pass"] }
|
|
include_examples "is verdict", :gray
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#action" do
|
|
it "enqueues a fail verdict" do
|
|
results = described_class.new("")
|
|
results.expects(:verdict).returns(:fail)
|
|
expect(results.action).to eq(:enqueue)
|
|
end
|
|
|
|
it "accepts a pass verdict" do
|
|
results = described_class.new("")
|
|
results.expects(:verdict).returns(:pass)
|
|
expect(results.action).to eq(:accept)
|
|
end
|
|
|
|
it "accepts a gray verdict" do
|
|
results = described_class.new("")
|
|
results.expects(:verdict).returns(:gray)
|
|
expect(results.action).to eq(:accept)
|
|
end
|
|
end
|
|
end
|