package api import ( "encoding/json" "fmt" "net/http" "net/http/httptest" "strings" "testing" ) func TestAdmissionEvidenceUTF8Limit(t *testing.T) { id := "sha256:" + strings.Repeat("d", 64) a, sid := runtimeTestAPI(t, func(w http.ResponseWriter, r *http.Request) { writeImageInspect(w, id, "0.2.0-rc.2") }) seedVerifiedImage(t, a, id, "0.2.0-rc.2") if _, err := a.s.DB.Exec("UPDATE users SET admin=1"); err != nil { t.Fatal(err) } for _, tc := range []struct { name, evidence string want int }{ {"ascii boundary", strings.Repeat("a", 4096), 200}, {"ascii over", strings.Repeat("a", 4097), 400}, {"Chinese regression", strings.Repeat("中", 1400), 400}, {"Chinese below", strings.Repeat("中", 1365), 200}, {"Chinese boundary", strings.Repeat("中", 1365) + "a", 200}, {"Chinese over", strings.Repeat("中", 1365) + "ab", 400}, {"emoji boundary", strings.Repeat("😀", 1024), 200}, {"emoji over", strings.Repeat("😀", 1024) + "a", 400}, {"two byte boundary", strings.Repeat("é", 2048), 200}, {"raw whitespace counts", " " + strings.Repeat("a", 4096), 400}, } { t.Run(tc.name, func(t *testing.T) { body, err := json.Marshal(map[string]string{"image_id": id, "state": "compatible", "evidence": tc.evidence}) if err != nil { t.Fatal(err) } w := httptest.NewRecorder() a.imageAdmission(w, runtimeRequest("POST", "/api/images/admission", string(body), sid)) if w.Code != tc.want { t.Fatal(w.Code, w.Body.String()) } if tc.want == 400 && !strings.Contains(w.Body.String(), "4096 UTF-8 bytes") { t.Fatal(w.Body.String()) } if tc.want == 200 { var evidence string if err := a.s.DB.QueryRow("SELECT evidence FROM image_admissions WHERE image_id=?", id).Scan(&evidence); err != nil || evidence != tc.evidence { t.Fatal(evidence, err) } } }) } } func TestAdmissionRoutesEnforceAuthorization(t *testing.T) { id := "sha256:" + strings.Repeat("d", 64) a, sid := runtimeTestAPI(t, func(w http.ResponseWriter, r *http.Request) { writeImageInspect(w, id, "0.2.0-rc.2") }) seedVerifiedImage(t, a, id, "0.2.0-rc.2") _, csrf, err := a.s.SessionUser(sid) if err != nil { t.Fatal(err) } body := fmt.Sprintf(`{"image_id":%q,"state":"compatible","evidence":"isolated validation reference"}`, id) request := func(method, path, payload string, auth, token bool) *httptest.ResponseRecorder { session := "" if auth { session = sid } r := runtimeRequest(method, path, payload, session) r.Header.Set("Origin", "http://panel.test") if token { r.Header.Set("X-CSRF-Token", csrf) } w := httptest.NewRecorder() a.Handler().ServeHTTP(w, r) return w } for _, path := range []string{"/api/latest", "/api/compatibility", "/api/intents", "/api/intents/old"} { if w := request("GET", path, "", true, false); w.Code != 404 { t.Fatal(path, w.Code, w.Body.String()) } } if w := request("POST", "/api/images/admission", body, true, true); w.Code != 403 { t.Fatal("nonadmin admission", w.Code, w.Body.String()) } if w := request("POST", "/api/builds", `{"version":"9.8.7","experimental":true}`, true, true); w.Code != 405 { t.Fatal("nonadmin experiment", w.Code, w.Body.String()) } if _, err = a.s.DB.Exec("UPDATE users SET admin=1 WHERE id='owner'"); err != nil { t.Fatal(err) } if w := request("POST", "/api/images/admission", body, true, false); w.Code != 403 { t.Fatal("missing CSRF", w.Code, w.Body.String()) } if w := request("POST", "/api/images/admission", body, true, true); w.Code != 200 { t.Fatal("admin admission", w.Code, w.Body.String()) } for _, payload := range []string{ body + "{}", body + strings.Repeat(" ", 8192), fmt.Sprintf(`{"image_id":%q,"state":"compatible","evidence":" "}`, id), fmt.Sprintf(`{"image_id":%q,"state":"available","evidence":"invalid state"}`, id), fmt.Sprintf(`{"image_id":%q,"state":"compatible","evidence":"test","dockerfile":"not allowed"}`, id), } { if w := request("POST", "/api/images/admission", payload, true, true); w.Code != 400 { t.Fatal(w.Code, w.Body.String()) } } var count int if err = a.s.DB.QueryRow("SELECT count(*) FROM audit WHERE action='image.admission'").Scan(&count); err != nil || count != 1 { t.Fatal(count, err) } }