package token import ( "net/http" "net/http/httptest" "testing" ) func TestNormalizeService(t *testing.T) { tests := []struct { name string in string want string }{ {"bare host", "atcr.io", "atcr.io"}, {"uppercase", "ATCR.io", "atcr.io"}, {"surrounding space", " atcr.io ", "atcr.io"}, {"host with port", "127.0.0.1:5000", "127.0.0.1"}, // The credential helper validates stored credentials against // appViewURL + "/auth/token?service=" + appViewURL, so ?service= // arrives as a full URL rather than a hostname. {"https url", "https://atcr.io", "atcr.io"}, {"http url with port", "http://127.0.0.1:5000", "127.0.0.1"}, {"url with path", "https://atcr.io/auth/token", "atcr.io"}, {"url with query", "https://atcr.io/auth/token?service=x", "atcr.io"}, {"bracketed ipv6 with port", "[::1]:5000", "::1"}, {"bracketed ipv6", "[::1]", "::1"}, {"empty", "", ""}, {"only space", " ", ""}, {"scheme only", "https://", ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := NormalizeService(tt.in); got != tt.want { t.Errorf("NormalizeService(%q) = %q, want %q", tt.in, got, tt.want) } }) } } func TestHandlerResolveService(t *testing.T) { const primary = "buoy.cr" services := []string{primary, "seamark.cr", "atcr.io"} tests := []struct { name string services []string query string host string want string description string }{ { name: "service param names a registry domain", services: services, query: "atcr.io", host: "seamark.dev", want: "atcr.io", description: "Docker echoes back the challenge's service; the realm lives on the UI host", }, { name: "service param as full url", services: services, query: "https://atcr.io", host: "seamark.dev", want: "atcr.io", description: "the credential helper sends the appview URL as ?service=", }, { name: "falls back to request host", services: services, query: "", host: "atcr.io", want: "atcr.io", description: "clients reaching /auth/token directly on a registry domain", }, { name: "unknown service param falls back to primary", services: services, query: "evil.example", host: "seamark.dev", want: primary, description: "the audience must never be caller-chosen", }, { name: "unknown service param does not beat a known host", services: services, query: "evil.example", host: "atcr.io", want: "atcr.io", }, { name: "ui host is not a registry domain", services: services, query: "", host: "seamark.dev", want: primary, }, { name: "no services configured", services: nil, query: "atcr.io", host: "atcr.io", want: primary, description: "single-domain deployments keep the issuer's service", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { h := &Handler{issuer: &Issuer{service: primary}} h.SetServices(tt.services) req := httptest.NewRequest(http.MethodGet, "/auth/token", nil) req.Host = tt.host if tt.query != "" { q := req.URL.Query() q.Set("service", tt.query) req.URL.RawQuery = q.Encode() } if got := h.resolveService(req, req.URL.Query().Get("service")); got != tt.want { t.Errorf("resolveService() = %q, want %q (%s)", got, tt.want, tt.description) } }) } } func TestHandlerSetServicesNormalizes(t *testing.T) { h := &Handler{issuer: &Issuer{service: "buoy.cr"}} h.SetServices([]string{"ATCR.io", "127.0.0.1:5000", " ", "seamark.cr"}) for _, want := range []string{"atcr.io", "127.0.0.1", "seamark.cr"} { if !h.services[want] { t.Errorf("services missing %q, got %v", want, h.services) } } if len(h.services) != 3 { t.Errorf("services = %v, want 3 entries", h.services) } }