Skip to content
quarry Artifact [07606e10e9]
Login

Artifact [07606e10e9]

Artifact 07606e10e9b720c154cc215d5e3019bc92c0c103a9b512b0b7438a506dfa8343:


package web

import (
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"craftdesign.group/quarry/internal/ops"
)

// fakeSessions resolves one cookie to one user and answers Allow from a
// role table keyed by repo. The empty repo is the site-admin question.
type fakeSessions struct {
	user  string
	roles map[string]ops.Auth
}

func (f fakeSessions) Login(string, string) (string, time.Time, bool) { return "", time.Time{}, false }
func (f fakeSessions) Resolve(tok string) (string, bool)              { return f.user, tok == "good" }
func (f fakeSessions) Logout(string)                                  {}
func (f fakeSessions) Allow(user, repo string, need ops.Auth) bool {
	if user != f.user {
		return false
	}
	return f.roles[repo] >= need
}

func withCookie(path string) *http.Request {
	req := httptest.NewRequest("GET", path, nil)
	req.AddCookie(&http.Cookie{Name: sessionCookie, Value: "good"})
	return req
}

// The fleet pages are gated at Admin with an empty repo. A user with
// write — even ADMIN — on a repository is not thereby a site admin, and
// the fleet surface exposes every store's operational detail. Getting
// this wrong turns "can edit one wiki" into "can read the whole fleet's
// error strings and force syncs across every store".
func TestFleetGateRefusesNonSiteAdmins(t *testing.T) {
	verify := Verify(func(string, string, string, ops.Auth) bool { return false })

	for _, tc := range []struct {
		name  string
		roles map[string]ops.Auth
		want  bool
	}{
		{"write on one repo", map[string]ops.Auth{"alpha": ops.Token}, false},
		{"admin on one repo", map[string]ops.Auth{"alpha": ops.Admin}, false},
		{"no roles at all", map[string]ops.Auth{}, false},
		{"site admin", map[string]ops.Auth{"": ops.Admin}, true},
	} {
		t.Run(tc.name, func(t *testing.T) {
			sess := fakeSessions{user: "someone", roles: tc.roles}
			w := httptest.NewRecorder()
			_, ok := gateAt(verify, sess, "", ops.Admin, w, withCookie("/fleet"))
			if ok != tc.want {
				t.Fatalf("gateAt admitted=%v, want %v (status %d)", ok, tc.want, w.Code)
			}
			if !tc.want && w.Code != http.StatusForbidden {
				t.Fatalf("refusal status = %d, want 403", w.Code)
			}
		})
	}
}

// A site admin must still be refused the per-repo write gate on a repo
// they hold no role for — gateAt must not have widened gate's meaning.
// (Site admins DO get every repo in the real role table; this asserts
// the level is what is consulted, not that admins are special-cased in
// the web layer.)
func TestGateStillAsksForTokenNotAdmin(t *testing.T) {
	verify := Verify(func(string, string, string, ops.Auth) bool { return false })
	sess := fakeSessions{user: "someone", roles: map[string]ops.Auth{"alpha": ops.Token}}

	if _, ok := gate(verify, sess, "alpha", httptest.NewRecorder(), withCookie("/r/alpha/wiki/x/edit")); !ok {
		t.Fatal("token-level user refused a write page")
	}
	w := httptest.NewRecorder()
	if _, ok := gate(verify, sess, "beta", w, withCookie("/r/beta/wiki/x/edit")); ok {
		t.Fatal("user with no role on beta admitted to beta's write page")
	}
	if w.Code != http.StatusForbidden {
		t.Fatalf("refusal status = %d, want 403", w.Code)
	}
}

// The refusal message must not name a repository when the gate was not
// scoped to one: "someone has no write access to ''" is the shape that
// sends an operator hunting for a repo with an empty name.
func TestForbidWordsMatchTheGate(t *testing.T) {
	for _, tc := range []struct {
		repo  string
		level ops.Auth
		want  string
	}{
		{"", ops.Admin, "someone is not a site admin"},
		{"alpha", ops.Admin, "someone has no admin access to alpha"},
		{"alpha", ops.Token, "someone has no write access to alpha"},
	} {
		w := httptest.NewRecorder()
		forbid(w, "someone", tc.repo, tc.level)
		if got := w.Body.String(); got != tc.want {
			t.Errorf("forbid(%q, %v) = %q, want %q", tc.repo, tc.level, got, tc.want)
		}
	}
}