# 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