Files
LambdaMail/services/protocols/internal/presentation/http/bimi_api_test.go
T
Lucas RayanandClaude Sonnet 5.5 60c6f5731c fix(bimi): each domain's logo under its own URL, and the publisher actually wired
Two bugs found by the setup check right after the last deploy:

- every domain's BIMI record named the deployment's own mail host, and the
  server picked the domain from the host name, so only the primary domain's
  logo could be fetched: ciberlab.seg.br's record pointed at a 404. The logo
  and the certificate are now served at /.well-known/bimi/<domain>/..., the
  record names that, and the webmail routes it. The old path still answers
- SetBimiPublisher ran before SetBimiAPI, found no BIMI API to attach to and
  was dropped, so saving a logo never published the DNS record by itself. It is
  kept on the router now, so the order does not matter

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-16 18:49:52 -03:00

539 lines
20 KiB
Go

package httppresentation
import (
"context"
"encoding/json"
"errors"
"net"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"time"
"lambdamail/protocols/internal/domain/bimi"
)
// memoryBimiStore is the store without a database.
type memoryBimiStore struct {
logos map[string]*BimiLogo
}
func (m *memoryBimiStore) Save(_ context.Context, domain string, svg []byte, vmcURL string) (string, error) {
held := m.logos[domain]
logo := &BimiLogo{SVG: svg, ETag: "etag-" + domain, VmcURL: vmcURL}
if held != nil {
// Replacing the logo keeps a held certificate, as the real store does
logo.VmcPEM = held.VmcPEM
}
m.logos[domain] = logo
return logo.ETag, nil
}
func (m *memoryBimiStore) Load(_ context.Context, domain string) (*BimiLogo, error) {
return m.logos[domain], nil
}
func (m *memoryBimiStore) Delete(_ context.Context, domain string) error {
delete(m.logos, domain)
return nil
}
func (m *memoryBimiStore) SaveCertificate(_ context.Context, domain string, pem []byte) (bool, error) {
logo := m.logos[domain]
if logo == nil {
return false, nil
}
logo.VmcPEM = pem
return true, nil
}
func (m *memoryBimiStore) DeleteCertificate(_ context.Context, domain string) error {
if logo := m.logos[domain]; logo != nil {
logo.VmcPEM = nil
}
return nil
}
type fakeBimiDNS map[string][]string
func (f fakeBimiDNS) LookupTXT(_ context.Context, name string) ([]string, error) {
if records, ok := f[name]; ok {
return records, nil
}
return nil, errors.New("no such host")
}
func fixture(t *testing.T, name string) []byte {
t.Helper()
data, err := os.ReadFile("../../domain/bimi/testdata/" + name)
if err != nil {
t.Fatal(err)
}
return data
}
// The certificates in the fixtures are valid on this date.
var bimiTestNow = time.Date(2026, 10, 16, 12, 0, 0, 0, time.UTC)
func newBimiRouter(store *memoryBimiStore) *Router {
r := &Router{mux: http.NewServeMux()}
r.bimi = &bimiAPI{
store: store,
sessions: testVerifier(interopSecret),
defaultDomain: "ebay.com",
mailHost: "mail.ebay.com",
now: func() time.Time { return bimiTestNow },
dns: fakeBimiDNS{},
fetch: func(context.Context, string) (bimi.Response, error) {
return bimi.Response{}, errors.New("offline")
},
}
r.mux.HandleFunc("/.well-known/bimi/default.svg", r.handleBimiLogo)
r.mux.HandleFunc("/.well-known/bimi/vmc.pem", r.handleBimiCertificate)
r.mux.HandleFunc("/.well-known/bimi/", r.handleBimiPerDomain)
r.mux.HandleFunc("/api/v1/admin/bimi", r.handleBimiAdmin)
r.mux.HandleFunc("/api/v1/admin/bimi/certificate", r.handleBimiCertificateAdmin)
r.mux.HandleFunc("/api/v1/admin/bimi/check", r.handleBimiCheck)
return r
}
func bimiCall(r *Router, method, target, body string, authed bool) *httptest.ResponseRecorder {
req := httptest.NewRequest(method, target, strings.NewReader(body))
if authed {
req.Header.Set("Authorization", "Bearer "+adminSurfaceToken2())
}
rec := httptest.NewRecorder()
r.mux.ServeHTTP(rec, req)
return rec
}
func jsonBody(t *testing.T, rec *httptest.ResponseRecorder) map[string]any {
t.Helper()
var out map[string]any
if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil {
t.Fatalf("not JSON (%d): %s", rec.Code, rec.Body)
}
return out
}
func putLogo(t *testing.T, r *Router, svg []byte) *httptest.ResponseRecorder {
t.Helper()
payload, _ := json.Marshal(map[string]string{"svg": string(svg)})
return bimiCall(r, http.MethodPut, "/api/v1/admin/bimi?domain=ebay.com", string(payload), true)
}
func putCertificate(r *Router, pem []byte) *httptest.ResponseRecorder {
payload, _ := json.Marshal(map[string]string{"pem": string(pem)})
return bimiCall(r, http.MethodPut, "/api/v1/admin/bimi/certificate?domain=ebay.com", string(payload), true)
}
func TestBimiAdminRoutesNeedAnAdminSession(t *testing.T) {
r := newBimiRouter(&memoryBimiStore{logos: map[string]*BimiLogo{}})
for _, target := range []string{
"/api/v1/admin/bimi?domain=ebay.com",
"/api/v1/admin/bimi/certificate?domain=ebay.com",
"/api/v1/admin/bimi/check?domain=ebay.com",
} {
for _, method := range []string{http.MethodGet, http.MethodPut, http.MethodDelete} {
if rec := bimiCall(r, method, target, "{}", false); rec.Code != http.StatusUnauthorized {
t.Errorf("%s %s without a session answered %d", method, target, rec.Code)
}
}
}
if rec := bimiCall(r, http.MethodGet, "/api/v1/admin/bimi/check", "", true); rec.Code != http.StatusBadRequest {
t.Errorf("no domain answered %d", rec.Code)
}
}
func TestBimiCertificateNeedsALogoFirst(t *testing.T) {
r := newBimiRouter(&memoryBimiStore{logos: map[string]*BimiLogo{}})
rec := putCertificate(r, fixture(t, "ebay.pem"))
if rec.Code != http.StatusConflict {
t.Fatalf("answered %d: %s", rec.Code, rec.Body)
}
}
func TestBimiAcceptsTheRealCertificateAndServesIt(t *testing.T) {
store := &memoryBimiStore{logos: map[string]*BimiLogo{}}
r := newBimiRouter(store)
if rec := putLogo(t, r, fixture(t, "ebay.svg")); rec.Code != http.StatusOK {
t.Fatalf("the real logo was refused: %s", rec.Body)
}
if rec := bimiCall(r, http.MethodGet, "/.well-known/bimi/vmc.pem", "", false); rec.Code != http.StatusNotFound {
t.Errorf("before upload the public URL answered %d, want 404", rec.Code)
}
rec := putCertificate(r, fixture(t, "ebay.pem"))
if rec.Code != http.StatusOK {
t.Fatalf("the real certificate was refused: %s", rec.Body)
}
cert := jsonBody(t, rec)["certificate"].(map[string]any)
if cert["valid"] != true || cert["matches_logo"] != true || cert["kind"] != "vmc" {
t.Errorf("certificate report = %v", cert)
}
// The public document: no session, the PEM, cacheable, and conditional
public := bimiCall(r, http.MethodGet, "/.well-known/bimi/vmc.pem", "", false)
if public.Code != http.StatusOK || !strings.Contains(public.Body.String(), "BEGIN CERTIFICATE") {
t.Fatalf("public answered %d", public.Code)
}
if got := public.Header().Get("Content-Type"); got != "application/x-pem-file" {
t.Errorf("content type = %q", got)
}
if public.Header().Get("X-Content-Type-Options") != "nosniff" || !strings.Contains(public.Header().Get("Cache-Control"), "public") {
t.Errorf("headers = %v", public.Header())
}
conditional := httptest.NewRequest(http.MethodGet, "/.well-known/bimi/vmc.pem", nil)
conditional.Header.Set("If-None-Match", public.Header().Get("ETag"))
again := httptest.NewRecorder()
r.mux.ServeHTTP(again, conditional)
if again.Code != http.StatusNotModified {
t.Errorf("conditional request answered %d", again.Code)
}
// The console reads it back, with the URLs the DNS record should carry
got := jsonBody(t, bimiCall(r, http.MethodGet, "/api/v1/admin/bimi?domain=ebay.com", "", true))
if got["logo_url"] != "https://mail.ebay.com/.well-known/bimi/ebay.com/default.svg" || got["certificate_url"] != "https://mail.ebay.com/.well-known/bimi/ebay.com/vmc.pem" {
t.Errorf("urls = %v / %v", got["logo_url"], got["certificate_url"])
}
if got["certificate"].(map[string]any)["present"] != true {
t.Errorf("certificate = %v", got["certificate"])
}
// Removing it leaves the logo
if rec := bimiCall(r, http.MethodDelete, "/api/v1/admin/bimi/certificate?domain=ebay.com", "", true); rec.Code != http.StatusOK {
t.Fatalf("delete answered %d", rec.Code)
}
if rec := bimiCall(r, http.MethodGet, "/.well-known/bimi/vmc.pem", "", false); rec.Code != http.StatusNotFound {
t.Errorf("after delete the public URL answered %d", rec.Code)
}
if rec := bimiCall(r, http.MethodGet, "/.well-known/bimi/default.svg", "", false); rec.Code != http.StatusOK {
t.Errorf("the logo disappeared with the certificate: %d", rec.Code)
}
}
func TestBimiRefusesACertificateForAnotherLogo(t *testing.T) {
store := &memoryBimiStore{logos: map[string]*BimiLogo{}}
r := newBimiRouter(store)
// CNN's logo is valid SVG Tiny PS but is not the one eBay's certificate holds
if rec := putLogo(t, r, fixture(t, "cnn.svg")); rec.Code != http.StatusOK {
t.Fatalf("logo refused: %s", rec.Body)
}
rec := putCertificate(r, fixture(t, "ebay.pem"))
if rec.Code != http.StatusBadRequest {
t.Fatalf("answered %d: %s", rec.Code, rec.Body)
}
violations, _ := jsonBody(t, rec)["violations"].([]any)
if len(violations) == 0 || !strings.Contains(violations[0].(string), "logoMismatch") {
t.Errorf("violations = %v", violations)
}
if len(store.logos["ebay.com"].VmcPEM) != 0 {
t.Error("a certificate that does not match was stored")
}
}
func TestBimiNeverStoresAPrivateKey(t *testing.T) {
store := &memoryBimiStore{logos: map[string]*BimiLogo{}}
r := newBimiRouter(store)
putLogo(t, r, fixture(t, "ebay.svg"))
key := "-----BEGIN PRIVATE KEY-----\nQUJD\n-----END PRIVATE KEY-----\n"
rec := putCertificate(r, append(fixture(t, "ebay.pem"), key...))
if rec.Code != http.StatusBadRequest {
t.Fatalf("answered %d", rec.Code)
}
if len(store.logos["ebay.com"].VmcPEM) != 0 {
t.Error("a private key was stored and would be served publicly")
}
if strings.Contains(rec.Body.String(), "QUJD") {
t.Error("the refusal echoed the key")
}
}
func TestBimiCertificateBodyMustBeJSON(t *testing.T) {
r := newBimiRouter(&memoryBimiStore{logos: map[string]*BimiLogo{"ebay.com": {SVG: fixture(t, "ebay.svg")}}})
if rec := bimiCall(r, http.MethodPut, "/api/v1/admin/bimi/certificate?domain=ebay.com", "-----BEGIN CERTIFICATE-----", true); rec.Code != http.StatusBadRequest {
t.Errorf("answered %d", rec.Code)
}
}
func TestBimiReplacingTheLogoWarnsAboutTheHeldCertificate(t *testing.T) {
store := &memoryBimiStore{logos: map[string]*BimiLogo{}}
r := newBimiRouter(store)
putLogo(t, r, fixture(t, "ebay.svg"))
putCertificate(r, fixture(t, "ebay.pem"))
// Another valid logo: the certificate no longer vouches for what is served
rec := putLogo(t, r, fixture(t, "cnn.svg"))
if rec.Code != http.StatusOK || jsonBody(t, rec)["certificate_mismatch"] != true {
t.Fatalf("no warning: %d %s", rec.Code, rec.Body)
}
cert := jsonBody(t, bimiCall(r, http.MethodGet, "/api/v1/admin/bimi?domain=ebay.com", "", true))["certificate"].(map[string]any)
if cert["valid"] != false || cert["matches_logo"] != false {
t.Errorf("the console should show the certificate as no longer matching: %v", cert)
}
}
func TestBimiCheckReportsEachStep(t *testing.T) {
store := &memoryBimiStore{logos: map[string]*BimiLogo{}}
r := newBimiRouter(store)
putLogo(t, r, fixture(t, "ebay.svg"))
rec := bimiCall(r, http.MethodGet, "/api/v1/admin/bimi/check?domain=ebay.com", "", true)
body := jsonBody(t, rec)
if rec.Code != http.StatusOK || body["ready"] != false {
t.Fatalf("with nothing published it must not be ready: %d %v", rec.Code, body)
}
ids := map[string]string{}
for _, c := range body["checks"].([]any) {
m := c.(map[string]any)
ids[m["id"].(string)] = m["status"].(string)
}
if ids["logo"] != "ok" || ids["dmarc"] != "fail" || ids["record"] != "fail" {
t.Errorf("checks = %v", ids)
}
none := bimiCall(r, http.MethodGet, "/api/v1/admin/bimi/check?domain=unknown.test", "", true)
if jsonBody(t, none)["configured"] != false {
t.Errorf("a domain with no logo: %s", none.Body)
}
}
func TestPublicAddress(t *testing.T) {
for address, want := range map[string]bool{
"8.8.8.8": true,
"1.1.1.1": true,
"2001:4860::8888": true,
"127.0.0.1": false,
"10.0.0.5": false,
"172.16.3.4": false,
"192.168.1.1": false,
"169.254.169.254": false,
"100.64.0.1": false,
"0.0.0.0": false,
"::1": false,
"fe80::1": false,
"fc00::1": false,
"224.0.0.1": false,
} {
if got := publicAddress(net.ParseIP(address)); got != want {
t.Errorf("publicAddress(%s) = %v, want %v", address, got, want)
}
}
}
// The fetcher is what keeps the console's checks from reaching this server's
// own network. It refuses a literal private address before any request leaves.
func TestFetcherRefusesPrivateAndPlainHTTP(t *testing.T) {
fetch := newBimiFetcher()
for _, url := range []string{"http://example.test/logo.svg", "https://127.0.0.1/logo.svg", "https://169.254.169.254/latest/meta-data/", "https://[::1]/x"} {
if _, err := fetch(context.Background(), url); err == nil {
t.Errorf("%s was fetched", url)
}
}
}
// publishRecorder collects the domains the console asked to have published.
type publishRecorder struct{ domains chan string }
func newPublishRecorder() *publishRecorder { return &publishRecorder{domains: make(chan string, 8)} }
func (p *publishRecorder) record(domain string) { p.domains <- domain }
func (p *publishRecorder) expect(t *testing.T, what string) {
t.Helper()
select {
case got := <-p.domains:
if got != "ebay.com" {
t.Errorf("%s published %q", what, got)
}
case <-time.After(2 * time.Second):
t.Fatalf("%s did not ask for the DNS record to be published", what)
}
}
func (p *publishRecorder) expectNothing(t *testing.T, what string) {
t.Helper()
select {
case got := <-p.domains:
t.Fatalf("%s published %q though it changed nothing", what, got)
case <-time.After(150 * time.Millisecond):
}
}
// The record in the zone follows the picture: every change to the logo or the
// certificate asks for the DNS to be reconciled, and nothing else does.
func TestBimiChangesAreHandedToThePublisher(t *testing.T) {
store := &memoryBimiStore{logos: map[string]*BimiLogo{}}
r := newBimiRouter(store)
rec := newPublishRecorder()
r.bimi.publish = rec.record
putLogo(t, r, fixture(t, "ebay.svg"))
rec.expect(t, "saving the logo")
putCertificate(r, fixture(t, "ebay.pem"))
rec.expect(t, "saving the certificate")
// Reads and refused uploads change nothing and publish nothing
bimiCall(r, http.MethodGet, "/api/v1/admin/bimi?domain=ebay.com", "", true)
putCertificate(r, []byte("-----BEGIN PRIVATE KEY-----\nQUJD\n-----END PRIVATE KEY-----\n"))
putLogo(t, r, []byte("<svg/>"))
rec.expectNothing(t, "a read or a refused upload")
bimiCall(r, http.MethodDelete, "/api/v1/admin/bimi/certificate?domain=ebay.com", "", true)
rec.expect(t, "removing the certificate")
bimiCall(r, http.MethodDelete, "/api/v1/admin/bimi?domain=ebay.com", "", true)
rec.expect(t, "removing the logo")
}
func TestBimiWithoutAPublisherStillWorks(t *testing.T) {
r := newBimiRouter(&memoryBimiStore{logos: map[string]*BimiLogo{}})
resp := jsonBody(t, putLogo(t, r, fixture(t, "ebay.svg")))
if resp["ok"] != true || resp["dns_publishing"] != false {
t.Fatalf("%v", resp)
}
}
// The check expects what the reconciler writes: with a certificate that no
// longer matches the logo that is a record without a=, and that is correct.
func TestBimiCheckExpectsWhatTheReconcilerPublishes(t *testing.T) {
store := &memoryBimiStore{logos: map[string]*BimiLogo{}}
r := newBimiRouter(store)
putLogo(t, r, fixture(t, "cnn.svg"))
store.logos["ebay.com"].VmcPEM = fixture(t, "ebay.pem") // a certificate for another picture
r.bimi.dns = fakeBimiDNS{
"_dmarc.ebay.com": {"v=DMARC1; p=reject"},
"default._bimi.ebay.com": {"v=BIMI1; l=https://mail.ebay.com/.well-known/bimi/ebay.com/default.svg;"},
}
body := jsonBody(t, bimiCall(r, http.MethodGet, "/api/v1/admin/bimi/check?domain=ebay.com", "", true))
status := map[string]string{}
detail := map[string]string{}
for _, c := range body["checks"].([]any) {
m := c.(map[string]any)
status[m["id"].(string)] = m["status"].(string)
detail[m["id"].(string)] = m["detail"].(string)
}
if status["record"] != "ok" {
t.Errorf("the record without a= is what the reconciler writes here, but the check said %s: %s", status["record"], detail["record"])
}
if status["certificate"] != "fail" || !strings.Contains(detail["certificate"], "NOT published") {
t.Errorf("the certificate check must say it is held back: %s %s", status["certificate"], detail["certificate"])
}
}
// The console shows the record the reconciler writes, taken from the server
// rather than put together again in the browser.
func TestBimiGetReportsTheRecordItWouldPublish(t *testing.T) {
store := &memoryBimiStore{logos: map[string]*BimiLogo{}}
r := newBimiRouter(store)
putLogo(t, r, fixture(t, "ebay.svg"))
read := func() map[string]any {
return jsonBody(t, bimiCall(r, http.MethodGet, "/api/v1/admin/bimi?domain=ebay.com", "", true))
}
got := read()
if got["dns_record"] != "v=BIMI1; l=https://mail.ebay.com/.well-known/bimi/ebay.com/default.svg;" || got["certificate_withheld"] != "" {
t.Fatalf("logo only: %v", got)
}
putCertificate(r, fixture(t, "ebay.pem"))
if got := read(); got["dns_record"] != "v=BIMI1; l=https://mail.ebay.com/.well-known/bimi/ebay.com/default.svg; a=https://mail.ebay.com/.well-known/bimi/ebay.com/vmc.pem;" {
t.Fatalf("with its certificate: %v", got["dns_record"])
}
// The logo is replaced: the certificate is held but no longer advertised
putLogo(t, r, fixture(t, "cnn.svg"))
got = read()
if got["certificate_withheld"] != "logoMismatch" || strings.Contains(got["dns_record"].(string), "a=") {
t.Fatalf("after replacing the logo: %v / %v", got["dns_record"], got["certificate_withheld"])
}
}
// The bug this pins: every domain's record named the deployment's own mail host,
// and the host chose the domain, so only the primary domain's logo was ever
// found. A second domain's logo has to be served under its own name.
func TestBimiServesEachDomainsLogoUnderItsOwnName(t *testing.T) {
store := &memoryBimiStore{logos: map[string]*BimiLogo{
"ebay.com": {SVG: fixture(t, "ebay.svg"), ETag: "e1"},
"cnn.com": {SVG: fixture(t, "cnn.svg"), ETag: "c1", VmcPEM: fixture(t, "ebay.pem")},
}}
r := newBimiRouter(store) // its default domain, and the host these requests arrive on, is ebay.com
logo := bimiCall(r, http.MethodGet, "/.well-known/bimi/cnn.com/default.svg", "", false)
if logo.Code != http.StatusOK || logo.Body.String() != string(fixture(t, "cnn.svg")) {
t.Fatalf("the second domain's logo: %d", logo.Code)
}
if got := logo.Header().Get("Content-Type"); got != "image/svg+xml" {
t.Errorf("content type = %q", got)
}
if rec := bimiCall(r, http.MethodGet, "/.well-known/bimi/cnn.com/vmc.pem", "", false); rec.Code != http.StatusOK || !strings.Contains(rec.Body.String(), "BEGIN CERTIFICATE") {
t.Errorf("the second domain's certificate: %d", rec.Code)
}
// Case does not matter in a host name
if rec := bimiCall(r, http.MethodGet, "/.well-known/bimi/CNN.com/default.svg", "", false); rec.Code != http.StatusOK {
t.Errorf("upper case: %d", rec.Code)
}
// The legacy path still answers with the host's own domain
if rec := bimiCall(r, http.MethodGet, "/.well-known/bimi/default.svg", "", false); rec.Code != http.StatusOK || rec.Body.String() != string(fixture(t, "ebay.svg")) {
t.Errorf("legacy path: %d", rec.Code)
}
}
func TestBimiPerDomainPathsRefuseWhatIsNotADomainLogo(t *testing.T) {
store := &memoryBimiStore{logos: map[string]*BimiLogo{"ebay.com": {SVG: fixture(t, "ebay.svg"), ETag: "e1"}}}
r := newBimiRouter(store)
for _, path := range []string{
"/.well-known/bimi/nobody.test/default.svg", // a domain with no logo
"/.well-known/bimi/ebay.com/other.svg", // not a document we serve
"/.well-known/bimi/ebay.com/", // no document
"/.well-known/bimi/ebay.com/x/default.svg", // extra segment
"/.well-known/bimi/ebay.com/default.svg/x",
"/.well-known/bimi/-bad.com/default.svg", // not a host name
"/.well-known/bimi/a_b.com/default.svg",
"/.well-known/bimi/localhost/default.svg", // a single label is not a mail domain
"/.well-known/bimi/%2e%2e/default.svg",
} {
if rec := bimiCall(r, http.MethodGet, path, "", false); rec.Code != http.StatusNotFound {
t.Errorf("%s answered %d, want 404", path, rec.Code)
}
}
if rec := bimiCall(r, http.MethodPost, "/.well-known/bimi/ebay.com/default.svg", "", false); rec.Code != http.StatusMethodNotAllowed {
t.Errorf("POST answered %d", rec.Code)
}
}
// The wiring bug: the publisher was handed over before the BIMI API existed and
// was dropped. Either order has to work.
func TestBimiPublisherSurvivesTheOrderItIsWiredIn(t *testing.T) {
store := &memoryBimiStore{logos: map[string]*BimiLogo{}}
publisher := func(string) {}
before := &Router{mux: http.NewServeMux()}
before.SetBimiPublisher(publisher)
before.SetBimiAPI(store, "secret", "ebay.com", "mail.ebay.com")
if before.bimi.publish == nil {
t.Error("a publisher set before the API was lost")
}
after := &Router{mux: http.NewServeMux()}
after.SetBimiAPI(store, "secret", "ebay.com", "mail.ebay.com")
after.SetBimiPublisher(publisher)
if after.bimi.publish == nil {
t.Error("a publisher set after the API was lost")
}
none := &Router{mux: http.NewServeMux()}
none.SetBimiAPI(store, "secret", "ebay.com", "mail.ebay.com")
if none.bimi.publish != nil {
t.Error("with no DNS provider there is nothing to publish to")
}
}