oficina / sheets Public

Clone
git clone https://amadan.net/oficina/sheets

Plain git — no account needed to clone.

Download

Download this file

1package sheets
2
3import (
4 "fmt"
5 "net/http"
6 "net/http/httptest"
7 "net/url"
8 "strings"
9 "testing"
10)
11
12func httpGET(path string) *http.Request {
13 return httptest.NewRequest(http.MethodGet, path, nil)
14}
15
16// teammateOn is somebody with BOTH an explicit grant and team standing
17// — a colleague who was also shared with directly. Team standing is
18// what makes revoking the grant not the same as removing their access,
19// so it is the whole point of the fixture.
20func teammateOn(t *testing.T, app *App, sheetID uint64, address string, role SheetRole) Member {
21 t.Helper()
22 m := guestOn(t, app, sheetID, address, role)
23 if err := app.db.Model(&Member{}).Where("id = ?", m.ID).
24 Update("team_role", string(TeamMember)).Error; err != nil {
25 t.Fatalf("put %s on the team: %v", address, err)
26 }
27 m.TeamRole = string(TeamMember)
28 return m
29}
30
31// Removing a teammate's explicit grant removes the grant and does not
32// remove their access: team standing reaches every sheet on the
33// instance as an editor, by design (spec §8.2). The fallback is right
34// and stays. Saying "Access removed." on top of it is not.
35//
36// An owner who reads that and walks away believes they have shut
37// somebody out of a sealed sheet. They have not, and nothing on the
38// screen tells them.
39func TestRevokingATeammatesGrantDoesNotClaimTheyLostAccess(t *testing.T) {
40 app, cookies := signIn(t)
41 s, _ := newSheet(t, app, cookies, "Budget")
42 m := teammateOn(t, app, s.ID, "ada@example.com", SheetViewer)
43
44 form := url.Values{"member": {fmt.Sprint(m.ID)}}
45 rec := do(t, app, as(postForm(sharePath(s)+"/revoke", form), cookies))
46 if rec.Code != http.StatusSeeOther {
47 t.Fatalf("revoke = %d: %s", rec.Code, rec.Body.String())
48 }
49
50 // The premise. If this ever stops holding, the notice below is not
51 // the thing to change — the fallback is.
52 if !app.mayWrite(s.ID, m) {
53 t.Fatal("a team member lost access when their explicit grant went; " +
54 "team standing is supposed to reach every sheet")
55 }
56
57 loc := rec.Header().Get("Location")
58 if strings.HasSuffix(loc, "notice=revoked") {
59 t.Fatalf("Location = %q — told the owner access was removed while "+
60 "team standing still admits the person", loc)
61 }
62 if !strings.Contains(loc, "notice=revoked-team") {
63 t.Fatalf("Location = %q, want the notice that says they still reach it", loc)
64 }
65
66 // An email address must not travel in the query — that is the same
67 // access-log leak the share token was just moved out of. The id
68 // does, and the address is read back from it server-side.
69 if strings.Contains(loc, "ada@example.com") ||
70 strings.Contains(loc, url.QueryEscape("ada@example.com")) {
71 t.Fatalf("Location = %q carries an email address", loc)
72 }
73
74 // The sentence has to name them, because revoking the grant also
75 // takes them off the list below: without a name the notice refers
76 // to somebody the owner can no longer see on the screen.
77 page := do(t, app, as(httpGET(loc), cookies)).Body.String()
78 if !strings.Contains(page, "ada@example.com") {
79 t.Fatal("the notice does not name the person it is about")
80 }
81 if !strings.Contains(page, "on the team") {
82 t.Fatalf("the notice does not say why they still reach it")
83 }
84}
85
86// The plain case has to keep the plain sentence: a guest with no team
87// standing really does lose the sheet, and a fix that hedged every
88// revoke would be its own kind of lie.
89func TestRevokingAGuestsGrantStillReportsAPlainRemoval(t *testing.T) {
90 app, cookies := signIn(t)
91 s, _ := newSheet(t, app, cookies, "Budget")
92 m := guestOn(t, app, s.ID, "ada@elsewhere.example", SheetViewer)
93
94 form := url.Values{"member": {fmt.Sprint(m.ID)}}
95 rec := do(t, app, as(postForm(sharePath(s)+"/revoke", form), cookies))
96
97 if app.mayRead(s.ID, m) {
98 t.Fatal("a guest kept access after their only grant was revoked")
99 }
100 if loc := rec.Header().Get("Location"); !strings.Contains(loc, "notice=revoked") ||
101 strings.Contains(loc, "revoked-team") {
102 t.Fatalf("Location = %q, want the plain removal notice", loc)
103 }
104}
105