package main // The commit and the reads act as the tools' own account (novox/hq ADR 0365 ยง2, issue 469): the token the module // keeps for `mesh-tools`, never the admin login. That account is no repository's administrator, so the forge // refuses it the protection rules (403, as gitea's reqAdmin does); the commit then judges an existing branch by the // protection the forge says it applies, and leaves a branch not made yet to the forge's own judgement. import ( "context" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" "gitea/internal/forgehttp" ) const toolsToken = "tools-token-never-shown" // asTools serves the fake forge to a bundle holding the tools' token: only that token is taken, the rules are // refused as to a non-administrator, and the branches named protected say so as the forge does. func asTools(t *testing.T, f *fakeForge, protected map[string]string, scopes string) (*Forge, *[]string) { dir := t.TempDir() file := filepath.Join(dir, "token") if err := os.WriteFile(file, []byte(toolsToken+"\n"), 0o600); err != nil { t.Fatal(err) } if err := os.WriteFile(file+".scopes", []byte(scopes+"\n"), 0o600); err != nil { t.Fatal(err) } var seen []string const repo = "/api/v1/repos/acme/app" srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { seen = append(seen, r.Method+" "+r.URL.Path) if _, _, basic := r.BasicAuth(); basic || r.Header.Get("Authorization") != "token "+toolsToken { http.Error(w, "unauthorized", 401) return } if r.URL.Path == repo+"/branch_protections" { http.Error(w, `{"message":"token does not have the administration of this repository"}`, 403) return } if name := strings.TrimPrefix(r.URL.Path, repo+"/branches/"); r.Method == "GET" && name != r.URL.Path { if rule, ok := protected[name]; ok { _, _ = w.Write([]byte(`{"name":"` + name + `","commit":{"id":"c0"},"protected":true,"effective_branch_protection_name":"` + rule + `"}`)) return } } r.SetBasicAuth("mesh-admin", f.password) // the fake's own door, past the token checked above f.handle(w, r) })) t.Cleanup(srv.Close) return &Forge{Client: forgehttp.Client{URL: srv.URL, TokenFile: file, Account: "mesh-tools"}}, &seen } func TestTheToolsAccountCommitsOnANewBranchWithItsTokenThoughTheRulesAreNotItsToRead(t *testing.T) { f := newFake(t) forge, _ := asTools(t, f, nil, "read:user,write:issue,write:repository for mesh-tools") got, err := commit(context.Background(), forge, ask(t, map[string]any{"branch": "fix/365", "files": filesArg(map[string]any{"path": "x", "content": "x"})})) if err != nil { t.Fatal(err) } if len(f.writes) != 1 || !got.Created { t.Fatalf("%d writes, %+v", len(f.writes), got) } } func TestTheToolsAccountIsRefusedAProtectedBranchTheForgeSaysItGuardsAndTheDefaultBranch(t *testing.T) { for branch, rule := range map[string]string{"release/1.0": "release/*", "fix/1-old": "fix/1-old", "main": ""} { f := newFake(t) protected := map[string]string{} if rule != "" { protected[branch] = rule } forge, _ := asTools(t, f, protected, "read:user,write:issue,write:repository for mesh-tools") _, err := commit(context.Background(), forge, ask(t, map[string]any{"branch": branch, "files": filesArg(map[string]any{"path": "x", "content": "x"})})) if err == nil || !strings.Contains(err.Error(), "refused") || !strings.Contains(err.Error(), "ADR 0266") { t.Errorf("%s: %v", branch, err) } if rule != "" && !strings.Contains(err.Error(), rule) { t.Errorf("%s: the rule %s is not named: %v", branch, rule, err) } if len(f.writes)+len(f.patches) != 0 { t.Errorf("%s: something was written", branch) } } } func TestATokenKeptForTheAdministratorIsNeverSent(t *testing.T) { f := newFake(t) // What the module kept before ADR 0365: the admin's token, its scopes line naming no account. forge, seen := asTools(t, f, nil, "read:user,write:issue,write:repository") _, err := commit(context.Background(), forge, ask(t, map[string]any{"branch": "fix/365", "files": filesArg(map[string]any{"path": "x", "content": "x"})})) if err == nil || !strings.Contains(err.Error(), "not mesh-tools's") { t.Fatalf("%v", err) } if len(*seen) != 0 { t.Fatalf("the forge was asked %v", *seen) } } func TestTheBundleHandedATokenFileNeedsNoAdminLogin(t *testing.T) { t.Setenv("MESH_GITEA_URL", "http://127.0.0.1:3000") t.Setenv("MESH_GITEA_TOKEN_FILE", "/state/token") t.Setenv("MESH_GITEA_TOOLS_USER", "") t.Setenv("MESH_GITEA_ADMIN_USER", "") t.Setenv("MESH_GITEA_ADMIN_PASSWORD_FILE", "") f, err := ForgeFromEnv() if err != nil { t.Fatal(err) } if f.TokenFile != "/state/token" || f.Account != "mesh-tools" || f.User != "" || f.PasswordFile != "" { t.Fatalf("%+v", f.Client) } }