package console import ( "context" "encoding/json" "errors" "net/http" "strings" "testing" "time" "github.com/carlosframework/platform/internal/release" ) // TestAuthorizeDoorShowsThenSigns: an owner of the ops account (paul) reads // the plan and signs exactly the digest he confirmed (R19). func TestAuthorizeDoorShowsThenSigns(t *testing.T) { fx := newDigestsFixture(t) if err := fx.store.Delete(context.Background(), release.PromotionRecordKey("bab", "km", "v1")); err != nil { t.Fatal(err) } st, out := getRaw(t, fx.ts.URL+"/api/cli/apps/bab/km/releases/v1/authorize", fx.operator) if st != http.StatusOK { t.Fatalf("plan: %d %s", st, out) } var plan struct { ManifestDigest string `json:"manifest_digest"` } if err := json.Unmarshal([]byte(out), &plan); err != nil || plan.ManifestDigest == "" { t.Fatalf("plan body %s: %v", out, err) } st, out = postRaw(t, fx.ts.URL+"/api/cli/apps/bab/km/releases/v1/authorize", map[string]string{"manifest_digest": "00"}, fx.operator) if st != http.StatusConflict { t.Fatalf("a confirmation for other bytes: %d %s", st, out) } st, out = postRaw(t, fx.ts.URL+"/api/cli/apps/bab/km/releases/v1/authorize", map[string]string{"manifest_digest": plan.ManifestDigest}, fx.operator) if st != http.StatusOK { t.Fatalf("sign: %d %s", st, out) } if rec, err := release.ReadPromotionRecord(context.Background(), fx.store, fx.pub, "bab", "km", "v1"); err != nil || !rec.Authorized { t.Fatalf("record = %+v, %v", rec, err) } } func TestAuthorizeDoorRefusesServiceCredentials(t *testing.T) { fx := newDigestsFixture(t) st, _ := getRaw(t, fx.ts.URL+"/api/cli/apps/bab/km/releases/v1/authorize", fx.serviceToken) if st != http.StatusNotFound && st != http.StatusForbidden { t.Fatalf("a service credential reached authorize: %d", st) } } // TestAuthorizeDoorSpendsTheWriteBudget: the POST signs a record inside the // app's exclusive mutation, so it draws on the per-person write budget, like // resign, before any store work. The GET is a read and stays unbudgeted. func TestAuthorizeDoorSpendsTheWriteBudget(t *testing.T) { fx := newDigestsFixture(t) if err := fx.store.Delete(context.Background(), release.PromotionRecordKey("bab", "km", "v1")); err != nil { t.Fatal(err) } fx.srv.cli.write = newRateLimiter(1, time.Minute) u := fx.ts.URL + "/api/cli/apps/bab/km/releases/v1/authorize" st, out := getRaw(t, u, fx.operator) if st != http.StatusOK { t.Fatalf("plan: %d %s", st, out) } var plan struct { ManifestDigest string `json:"manifest_digest"` } if err := json.Unmarshal([]byte(out), &plan); err != nil || plan.ManifestDigest == "" { t.Fatalf("plan body %s: %v", out, err) } if st, out := postRaw(t, u, map[string]string{"manifest_digest": "00"}, fx.operator); st != http.StatusConflict { t.Fatalf("first request: %d %s", st, out) } if st, out := getRaw(t, u, fx.operator); st != http.StatusOK { t.Fatalf("a read after the budget is spent: %d %s", st, out) } if st, out := postRaw(t, u, map[string]string{"manifest_digest": plan.ManifestDigest}, fx.operator); st != http.StatusTooManyRequests || jsonErrorText(t, out) != textTooManyRequests { t.Fatalf("second request over the budget: %d %s", st, out) } if _, err := release.ReadPromotionRecord(context.Background(), fx.store, fx.pub, "bab", "km", "v1"); !errors.Is(err, release.ErrNoPromotionRecord) { t.Fatalf("a refused request wrote a record: %v", err) } } // authorizeDigest is the digest `carlos releases authorize` would show for // km's v1, read straight from the store so a refused caller's POST can carry // the right confirmation. func authorizeDigest(t *testing.T, fx *digestsFixture) string { t.Helper() plan, err := release.PlanAuthorize(context.Background(), fx.store, "bab", "km", "v1") if err != nil { t.Fatal(err) } return plan.ManifestDigest } // requireAuthorizeRefused: the caller gets the system-operator gate's bare // 404 on the GET and on a POST carrying the right digest, and no record is // written. func requireAuthorizeRefused(t *testing.T, fx *digestsFixture, bearer, who string) { t.Helper() u := fx.ts.URL + "/api/cli/apps/bab/km/releases/v1/authorize" if st, out := getRaw(t, u, bearer); st != http.StatusNotFound || strings.TrimSpace(out) != "404 page not found" { t.Fatalf("%s read the authorize plan: %d %s", who, st, out) } if st, out := postRaw(t, u, map[string]string{"manifest_digest": authorizeDigest(t, fx)}, bearer); st != http.StatusNotFound || strings.TrimSpace(out) != "404 page not found" { t.Fatalf("%s signed an authorization: %d %s", who, st, out) } if _, err := release.ReadPromotionRecord(context.Background(), fx.store, fx.pub, "bab", "km", "v1"); !errors.Is(err, release.ErrNoPromotionRecord) { t.Fatalf("a refused authorize wrote a record: %v", err) } } // requireAuthorizeSigns: the caller reads the plan and signs it, and the // record is an authorization of that digest. func requireAuthorizeSigns(t *testing.T, fx *digestsFixture, bearer, who string) { t.Helper() u := fx.ts.URL + "/api/cli/apps/bab/km/releases/v1/authorize" st, out := getRaw(t, u, bearer) if st != http.StatusOK { t.Fatalf("%s: plan: %d %s", who, st, out) } var plan struct { ManifestDigest string `json:"manifest_digest"` } if err := json.Unmarshal([]byte(out), &plan); err != nil || plan.ManifestDigest == "" { t.Fatalf("%s: plan body %s: %v", who, out, err) } if st, out := postRaw(t, u, map[string]string{"manifest_digest": plan.ManifestDigest}, bearer); st != http.StatusOK { t.Fatalf("%s: sign: %d %s", who, st, out) } rec, err := release.ReadPromotionRecord(context.Background(), fx.store, fx.pub, "bab", "km", "v1") if err != nil || !rec.Authorized || rec.ManifestDigest != plan.ManifestDigest { t.Fatalf("%s: record = %+v, %v", who, rec, err) } } // TestAuthorizeDoorIsNotAMembersDoor (R19): alice is a member of bab, the // app's account, but owns nothing in the ops account and is not on // SystemOperators. An authorize record makes a shipped build installable on // every channel, so she gets the system gate's bare 404 and writes nothing. func TestAuthorizeDoorIsNotAMembersDoor(t *testing.T) { fx := newDigestsFixture(t) if err := fx.store.Delete(context.Background(), release.PromotionRecordKey("bab", "km", "v1")); err != nil { t.Fatal(err) } requireAuthorizeRefused(t, fx, fx.member, "a plain member") } // TestAuthorizeDoorOpensToSystemOperators (R19): an address on // SystemOperators that is a member of bab but no ops owner can read and // sign; the same address with SystemOperators empty is a plain member again. func TestAuthorizeDoorOpensToSystemOperators(t *testing.T) { fx := newDigestsFixture(t) if err := fx.store.Delete(context.Background(), release.PromotionRecordKey("bab", "km", "v1")); err != nil { t.Fatal(err) } fx.srv.cfg.SystemOperators = nil requireAuthorizeRefused(t, fx, fx.member, "alice off SystemOperators") fx.srv.cfg.SystemOperators = []string{"alice@keymail.dev"} requireAuthorizeSigns(t, fx, fx.member, "alice on SystemOperators") }