Skip to content
quarry Artifact [5a0ae39383]
Login

Artifact [5a0ae39383]

Artifact 5a0ae393832d0d5646971ecde30eeb0bf48d6bd7d0a94e643067d2b9b37a30c2:


package web

import (
	"os"
	"regexp"
	"strings"
	"testing"
)

// The fleet is an administrator's view of the whole forge, and every
// route under it is supposed to be admin-only. /fleet was. /fleet.frag
// was not, and it renders the same panel — so a signed-out stranger
// could read fifty kilobytes of the fleet page from the fragment while
// the page itself answered 303 to the login form.
//
// This is the third door-with-a-weaker-lock of its kind: an API gate
// that disagreed with the web gate, a sync path that never consulted
// the letters the page promised, and now a fragment that skipped the
// gate its page carries. Each was found by hand, one at a time, after
// shipping. The pattern underneath is always the same — a SECOND way
// in that nobody remembered to lock, because the lock lives in the
// handler rather than in the route.
//
// So this test refuses the class rather than the instance: a handler
// registered under /fleet must gate, whatever it is called and whoever
// adds it next. A source-level check rather than a request-level one
// because it has to hold for routes that do not exist yet — the point
// is to fail the day somebody adds the fourth door, not to re-prove
// the three we know about.
func TestFleetRoutesAreAdminGated(t *testing.T) {
	src, err := os.ReadFile("web.go")
	if err != nil {
		t.Fatal(err)
	}
	// Each registration and the body that follows it, up to the next
	// one. Good enough to see whether the gate is the first thing the
	// handler does, which is the only place it is any use.
	// EVERY registration, so a handler's body is bounded by the next
	// route of any path. Bounding by the next /fleet route instead let
	// the LAST one run to end-of-file and swallow some unrelated
	// handler's gateAt — the guard passed while the hole was open,
	// which is the same shape of mistake it exists to catch.
	any := regexp.MustCompile(`mux\.Handle(?:Func)?\("(?:GET|POST) ([^"]*)"`)
	all := any.FindAllSubmatchIndex(src, -1)
	if len(all) == 0 {
		t.Fatal("no routes found — this test has lost track of the file it guards")
	}
	var ungated []string
	for i, loc := range all {
		route := string(src[loc[2]:loc[3]])
		if !strings.HasPrefix(route, "/fleet") {
			continue
		}
		end := len(src)
		if i+1 < len(all) {
			end = all[i+1][0]
		}
		body := string(src[loc[1]:end])
		if !strings.Contains(body, "gateAt(") {
			ungated = append(ungated, route)
		}
	}
	if len(ungated) > 0 {
		t.Errorf("these /fleet routes do not gate, so a signed-out stranger reaches them:\n  %s\n"+
			"every route under /fleet is an admin view; add gateAt(verify, sessions, \"\", ops.Admin, w, req)",
			strings.Join(ungated, "\n  "))
	}
}

// The two that were actually open, named so a regression on either is
// reported as itself rather than as a count.
func TestFleetFragmentsGateLikeTheirPage(t *testing.T) {
	src, err := os.ReadFile("web.go")
	if err != nil {
		t.Fatal(err)
	}
	for _, route := range []string{"/fleet.frag", "/fleet.rev"} {
		i := strings.Index(string(src), `mux.HandleFunc("GET `+route+`"`)
		if i < 0 {
			t.Errorf("%s is gone — if it moved, move this guard with it", route)
			continue
		}
		rest := string(src[i:])
		if end := strings.Index(rest[1:], "mux.HandleFunc("); end >= 0 {
			rest = rest[:end]
		}
		if !strings.Contains(rest, "ops.Admin") {
			t.Errorf("%s does not gate on ops.Admin: it renders the /fleet panel and must be as hard to reach as /fleet", route)
		}
	}
}