carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package console
2
3import (
4 "context"
5 "encoding/json"
6 "errors"
7 "net/http"
8 "strings"
9 "testing"
10 "time"
11
12 "github.com/carlosframework/platform/internal/release"
13)
14
15// TestAuthorizeDoorShowsThenSigns: an owner of the ops account (paul) reads
16// the plan and signs exactly the digest he confirmed (R19).
17func TestAuthorizeDoorShowsThenSigns(t *testing.T) {
18 fx := newDigestsFixture(t)
19 if err := fx.store.Delete(context.Background(), release.PromotionRecordKey("bab", "km", "v1")); err != nil {
20 t.Fatal(err)
21 }
22 st, out := getRaw(t, fx.ts.URL+"/api/cli/apps/bab/km/releases/v1/authorize", fx.operator)
23 if st != http.StatusOK {
24 t.Fatalf("plan: %d %s", st, out)
25 }
26 var plan struct {
27 ManifestDigest string `json:"manifest_digest"`
28 }
29 if err := json.Unmarshal([]byte(out), &plan); err != nil || plan.ManifestDigest == "" {
30 t.Fatalf("plan body %s: %v", out, err)
31 }
32 st, out = postRaw(t, fx.ts.URL+"/api/cli/apps/bab/km/releases/v1/authorize", map[string]string{"manifest_digest": "00"}, fx.operator)
33 if st != http.StatusConflict {
34 t.Fatalf("a confirmation for other bytes: %d %s", st, out)
35 }
36 st, out = postRaw(t, fx.ts.URL+"/api/cli/apps/bab/km/releases/v1/authorize", map[string]string{"manifest_digest": plan.ManifestDigest}, fx.operator)
37 if st != http.StatusOK {
38 t.Fatalf("sign: %d %s", st, out)
39 }
40 if rec, err := release.ReadPromotionRecord(context.Background(), fx.store, fx.pub, "bab", "km", "v1"); err != nil || !rec.Authorized {
41 t.Fatalf("record = %+v, %v", rec, err)
42 }
43}
44
45func TestAuthorizeDoorRefusesServiceCredentials(t *testing.T) {
46 fx := newDigestsFixture(t)
47 st, _ := getRaw(t, fx.ts.URL+"/api/cli/apps/bab/km/releases/v1/authorize", fx.serviceToken)
48 if st != http.StatusNotFound && st != http.StatusForbidden {
49 t.Fatalf("a service credential reached authorize: %d", st)
50 }
51}
52
53// TestAuthorizeDoorSpendsTheWriteBudget: the POST signs a record inside the
54// app's exclusive mutation, so it draws on the per-person write budget, like
55// resign, before any store work. The GET is a read and stays unbudgeted.
56func TestAuthorizeDoorSpendsTheWriteBudget(t *testing.T) {
57 fx := newDigestsFixture(t)
58 if err := fx.store.Delete(context.Background(), release.PromotionRecordKey("bab", "km", "v1")); err != nil {
59 t.Fatal(err)
60 }
61 fx.srv.cli.write = newRateLimiter(1, time.Minute)
62 u := fx.ts.URL + "/api/cli/apps/bab/km/releases/v1/authorize"
63 st, out := getRaw(t, u, fx.operator)
64 if st != http.StatusOK {
65 t.Fatalf("plan: %d %s", st, out)
66 }
67 var plan struct {
68 ManifestDigest string `json:"manifest_digest"`
69 }
70 if err := json.Unmarshal([]byte(out), &plan); err != nil || plan.ManifestDigest == "" {
71 t.Fatalf("plan body %s: %v", out, err)
72 }
73 if st, out := postRaw(t, u, map[string]string{"manifest_digest": "00"}, fx.operator); st != http.StatusConflict {
74 t.Fatalf("first request: %d %s", st, out)
75 }
76 if st, out := getRaw(t, u, fx.operator); st != http.StatusOK {
77 t.Fatalf("a read after the budget is spent: %d %s", st, out)
78 }
79 if st, out := postRaw(t, u, map[string]string{"manifest_digest": plan.ManifestDigest}, fx.operator); st != http.StatusTooManyRequests || jsonErrorText(t, out) != textTooManyRequests {
80 t.Fatalf("second request over the budget: %d %s", st, out)
81 }
82 if _, err := release.ReadPromotionRecord(context.Background(), fx.store, fx.pub, "bab", "km", "v1"); !errors.Is(err, release.ErrNoPromotionRecord) {
83 t.Fatalf("a refused request wrote a record: %v", err)
84 }
85}
86
87// authorizeDigest is the digest `carlos releases authorize` would show for
88// km's v1, read straight from the store so a refused caller's POST can carry
89// the right confirmation.
90func authorizeDigest(t *testing.T, fx *digestsFixture) string {
91 t.Helper()
92 plan, err := release.PlanAuthorize(context.Background(), fx.store, "bab", "km", "v1")
93 if err != nil {
94 t.Fatal(err)
95 }
96 return plan.ManifestDigest
97}
98
99// requireAuthorizeRefused: the caller gets the system-operator gate's bare
100// 404 on the GET and on a POST carrying the right digest, and no record is
101// written.
102func requireAuthorizeRefused(t *testing.T, fx *digestsFixture, bearer, who string) {
103 t.Helper()
104 u := fx.ts.URL + "/api/cli/apps/bab/km/releases/v1/authorize"
105 if st, out := getRaw(t, u, bearer); st != http.StatusNotFound || strings.TrimSpace(out) != "404 page not found" {
106 t.Fatalf("%s read the authorize plan: %d %s", who, st, out)
107 }
108 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" {
109 t.Fatalf("%s signed an authorization: %d %s", who, st, out)
110 }
111 if _, err := release.ReadPromotionRecord(context.Background(), fx.store, fx.pub, "bab", "km", "v1"); !errors.Is(err, release.ErrNoPromotionRecord) {
112 t.Fatalf("a refused authorize wrote a record: %v", err)
113 }
114}
115
116// requireAuthorizeSigns: the caller reads the plan and signs it, and the
117// record is an authorization of that digest.
118func requireAuthorizeSigns(t *testing.T, fx *digestsFixture, bearer, who string) {
119 t.Helper()
120 u := fx.ts.URL + "/api/cli/apps/bab/km/releases/v1/authorize"
121 st, out := getRaw(t, u, bearer)
122 if st != http.StatusOK {
123 t.Fatalf("%s: plan: %d %s", who, st, out)
124 }
125 var plan struct {
126 ManifestDigest string `json:"manifest_digest"`
127 }
128 if err := json.Unmarshal([]byte(out), &plan); err != nil || plan.ManifestDigest == "" {
129 t.Fatalf("%s: plan body %s: %v", who, out, err)
130 }
131 if st, out := postRaw(t, u, map[string]string{"manifest_digest": plan.ManifestDigest}, bearer); st != http.StatusOK {
132 t.Fatalf("%s: sign: %d %s", who, st, out)
133 }
134 rec, err := release.ReadPromotionRecord(context.Background(), fx.store, fx.pub, "bab", "km", "v1")
135 if err != nil || !rec.Authorized || rec.ManifestDigest != plan.ManifestDigest {
136 t.Fatalf("%s: record = %+v, %v", who, rec, err)
137 }
138}
139
140// TestAuthorizeDoorIsNotAMembersDoor (R19): alice is a member of bab, the
141// app's account, but owns nothing in the ops account and is not on
142// SystemOperators. An authorize record makes a shipped build installable on
143// every channel, so she gets the system gate's bare 404 and writes nothing.
144func TestAuthorizeDoorIsNotAMembersDoor(t *testing.T) {
145 fx := newDigestsFixture(t)
146 if err := fx.store.Delete(context.Background(), release.PromotionRecordKey("bab", "km", "v1")); err != nil {
147 t.Fatal(err)
148 }
149 requireAuthorizeRefused(t, fx, fx.member, "a plain member")
150}
151
152// TestAuthorizeDoorOpensToSystemOperators (R19): an address on
153// SystemOperators that is a member of bab but no ops owner can read and
154// sign; the same address with SystemOperators empty is a plain member again.
155func TestAuthorizeDoorOpensToSystemOperators(t *testing.T) {
156 fx := newDigestsFixture(t)
157 if err := fx.store.Delete(context.Background(), release.PromotionRecordKey("bab", "km", "v1")); err != nil {
158 t.Fatal(err)
159 }
160 fx.srv.cfg.SystemOperators = nil
161 requireAuthorizeRefused(t, fx, fx.member, "alice off SystemOperators")
162 fx.srv.cfg.SystemOperators = []string{"alice@keymail.dev"}
163 requireAuthorizeSigns(t, fx, fx.member, "alice on SystemOperators")
164}
165