Skip to content
quarry Artifact [931b328d16]
Login

Artifact [931b328d16]

Artifact 931b328d162711830f77afe0711a05c73c57a8dbf0f9347455f651d4c6bdaaec:


package main

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

	"craftdesign.group/quarry/internal/api"
	"craftdesign.group/quarry/internal/oltp"
	"craftdesign.group/quarry/internal/ops"
	"craftdesign.group/quarry/internal/web"
)

// A SESSION OPENED BY A SIGN-IN LINK CARRIES ITS TOKEN'S CEILING TO THE
// PAGES, A PASSWORD SESSION CARRIES NO TOKEN, AND REVOKING THE TOKEN
// TAKES THE SESSION AWAY (RFC 0021, Web sign-in).
func TestALinkSessionIsCutToItsTokensCeiling(t *testing.T) {
	g := newDoorRig(t)
	db := g.rg.odb
	_, tok := g.mint(t, oltp.TokenSpec{Scopes: scopes(oltp.ScopeWeb), Ceiling: "o"})
	link, _, err := db.MintSigninLink("jan", tok.ID, "/")
	if err != nil {
		t.Fatal(err)
	}
	cookie, _, _, ok := db.RedeemSigninLink(link, "", "")
	if !ok {
		t.Fatal("FAIL: the link opened no session")
	}
	pwCookie, _, ok := db.NewSession("jan", doorPassword)
	if !ok {
		t.Fatal("FAIL: password sign-in")
	}

	// What the pages see: the token on the request context, if any.
	seen := func(cookieValue string) (hasToken bool, canO, canI bool) {
		var out struct{ has, o, i bool }
		h := g.doors.wrap(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			if tk, ok := ops.TokenOf(req.Context()); ok {
				out.has = true
				out.o = tk.Caps("vault").HasAny("o")
				out.i = tk.Caps("vault").HasAny("i")
			}
		}))
		req := httptest.NewRequest(http.MethodGet, "/r/vault", nil)
		req.AddCookie(&http.Cookie{Name: web.SessionCookie, Value: cookieValue})
		h.ServeHTTP(httptest.NewRecorder(), req)
		return out.has, out.o, out.i
	}
	if has, o, i := seen(cookie); !has || !o || i {
		t.Errorf("FAIL: link session token=%v o=%v i=%v; want a token holding o and not i (the account holds both)", has, o, i)
	}
	if has, _, _ := seen(pwCookie); has {
		t.Errorf("FAIL: a password session was given a token")
	}
	if err := db.RevokeToken("jan", tok.ID); err != nil {
		t.Fatal(err)
	}
	if has, _, _ := seen(cookie); has {
		t.Errorf("FAIL: the revoked token's session still carries it")
	}
	if _, ok := db.SessionAccount(cookie); ok {
		t.Errorf("FAIL: the revoked token's session still resolves")
	}
}

// THROUGH THE REAL REQUEST PATH, A LINK SESSION CEILINGED TO o IS REFUSED
// AT A SITE-ADMIN PAGE WHERE THE SAME ACCOUNT'S PASSWORD SESSION IS NOT.
func TestLinkSessionIsRefusedAtASiteAdminPage(t *testing.T) {
	g := newDoorRig(t)
	db := g.rg.odb
	if err := db.AddAccount("boss", doorPassword, true); err != nil {
		t.Fatal(err)
	}
	_, tok, err := db.MintToken("boss", oltp.TokenSpec{Name: "t", Scopes: scopes(oltp.ScopeWeb), Ceiling: "o"})
	if err != nil {
		t.Fatal(err)
	}
	link, _, err := db.MintSigninLink("boss", tok.ID, "/")
	if err != nil {
		t.Fatal(err)
	}
	linkCookie, _, _, ok := db.RedeemSigninLink(link, "", "")
	if !ok {
		t.Fatal("FAIL: the link opened no session")
	}
	pwCookie, _, ok := db.NewSession("boss", doorPassword)
	if !ok {
		t.Fatal("FAIL: password sign-in")
	}
	r := ops.NewRegistry()
	if err := api.RegisterAll(r, api.Deps{Version: "v", Accounts: accountStore{db: db}, Repos: api.NewRepos(map[string]*api.Repo{})}); err != nil {
		t.Fatal(err)
	}
	deny := func(string, string, string, ops.Auth) api.Decision { return api.Denied }
	h := g.doors.wrap(web.HandlerWith(r, nil, nil,
		[]web.Option{web.WithSessions(sessionPlane{db: db}), web.WithCaps(capsPlane{db: db})}, web.Verify(deny)))
	get := func(cookie string) int {
		req := httptest.NewRequest(http.MethodGet, "/fleet/people", nil)
		req.AddCookie(&http.Cookie{Name: web.SessionCookie, Value: cookie})
		w := httptest.NewRecorder()
		h.ServeHTTP(w, req)
		return w.Code
	}
	if code := get(pwCookie); code != http.StatusOK {
		t.Fatalf("FAIL: a site admin's password session at /fleet/people: %d; want 200", code)
	}
	if code := get(linkCookie); code != http.StatusForbidden {
		t.Errorf("FAIL: a link session ceilinged to o at /fleet/people: %d; want 403", code)
	}
}