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 "io"
5 "net/http"
6 "regexp"
7 "strings"
8 "testing"
9 "time"
10
11 "github.com/carlosframework/platform/internal/svcrole"
12)
13
14type rawReply struct {
15 status int
16 body string
17}
18
19// doRaw sends one request with an optional bearer token. Non-GET methods
20// carry the body {}.
21func doRaw(t *testing.T, method, url, tok string) rawReply {
22 t.Helper()
23 var body io.Reader
24 if method != http.MethodGet && method != http.MethodHead {
25 body = strings.NewReader("{}")
26 }
27 req, err := http.NewRequest(method, url, body)
28 if err != nil {
29 t.Fatal(err)
30 }
31 if body != nil {
32 req.Header.Set("Content-Type", "application/json")
33 }
34 if tok != "" {
35 req.Header.Set("Authorization", "Bearer "+tok)
36 }
37 resp, err := http.DefaultClient.Do(req)
38 if err != nil {
39 t.Fatal(err)
40 }
41 defer resp.Body.Close()
42 b, err := io.ReadAll(resp.Body)
43 if err != nil {
44 t.Fatal(err)
45 }
46 return rawReply{status: resp.StatusCode, body: string(b)}
47}
48
49// TestUnknownStoredRoleIsNotSignedInAtEveryDoor is spec §9's first check: a
50// console that does not know a stored role (as v0.21.0 does not know
51// deploy) answers its token exactly as it answers no token at all.
52func TestUnknownStoredRoleIsNotSignedInAtEveryDoor(t *testing.T) {
53 fx := newConsoleFixture(t)
54 if _, err := fx.st.CreateAppIn(OpsAccountID, "hello", "paul@keymail.dev", time.Now()); err != nil {
55 t.Fatal(err)
56 }
57 raw, err := fx.st.NewServicePrincipalScoped(OpsAccountID, "ci", "hello", svcrole.Deploy, []string{"edge"}, nil, "paul@keymail.dev", time.Now())
58 if err != nil {
59 t.Fatal(err)
60 }
61 // Positive control: before the role is rewritten the token authenticates
62 // as deploy, so a deploy door does not answer 401.
63 if pre := doRaw(t, http.MethodGet, fx.ts.URL+"/api/cli/apps/bab/hello/channels", raw); pre.status == http.StatusUnauthorized {
64 t.Fatalf("deploy token was refused before the role change: %d %q", pre.status, pre.body)
65 }
66 res, err := fx.st.db.Exec(`UPDATE cli_tokens SET role = 'futurerole' WHERE token_hash = ?`, hashToken(raw))
67 if err != nil {
68 t.Fatal(err)
69 }
70 if n, err := res.RowsAffected(); err != nil || n != 1 {
71 t.Fatalf("role rewrite changed %d rows (err %v), want 1", n, err)
72 }
73 if len(fx.srv.cliRoles) == 0 {
74 t.Fatal("no registered CLI doors to check")
75 }
76 fill := regexp.MustCompile(`\{[a-z]+\}`)
77 for pattern := range fx.srv.cliRoles {
78 method, path, _ := strings.Cut(pattern, " ")
79 path = strings.Replace(path, "{account}", "bab", 1)
80 path = strings.Replace(path, "{app}", "hello", 1)
81 path = fill.ReplaceAllString(path, "x")
82 with := doRaw(t, method, fx.ts.URL+path, raw)
83 without := doRaw(t, method, fx.ts.URL+path, "")
84 // The no-token side must be the gate's own refusal, so an unrouted
85 // path (404/405 on both sides) cannot pass for a door.
86 if without.status != http.StatusUnauthorized || !strings.Contains(without.body, "not signed in") {
87 t.Errorf("%s: no token answered %d %q, want the gate's 401 not signed in", pattern, without.status, without.body)
88 continue
89 }
90 if with.status != without.status || with.body != without.body {
91 t.Errorf("%s: unknown role answered %d %q; no token answered %d %q",
92 pattern, with.status, with.body, without.status, without.body)
93 }
94 }
95}
96