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 "net/http"
5 "strconv"
6)
7
8// noticeRevokedTeam is the code for a revoke that removed a grant and
9// did not remove anybody's access.
10//
11// Team standing reaches every sheet on the instance as an editor, by
12// design (spec §8.2), and an explicit grant sits on top of it. So
13// removing a teammate's grant takes the grant away and leaves the
14// person able to open the sheet. The screen used to report the first
15// half and stay silent about the second, which let an owner read
16// "Access removed", walk away, and believe they had shut somebody out
17// of a sealed sheet.
18//
19// RULED by Paul, 2026-09-01: the sentence is what changes here, not the
20// behaviour. Removing a restricting grant from a teammate still widens
21// their access from viewer to editor — see the issue filed against this
22// commit, which records that as a separate, open question rather than
23// letting it disappear behind a notice that now describes it politely.
24const noticeRevokedTeam = "revoked-team"
25
26// revokedTeamText is the sentence when we cannot name the person.
27//
28// Paul's wording, with the name generalised for the fallback below.
29const revokedTeamText = "The explicit permission is removed, but since they are " +
30 "on the team, they still have access."
31
32// stillReaches reports whether somebody can still open a sheet.
33//
34// Asked AFTER the revoke, and asked of roleFor rather than reasoned
35// about separately, so the notice reports what the gate actually says
36// instead of holding a second opinion that can drift from it.
37func (a *App) stillReaches(sheetID, memberID uint64) bool {
38 m, ok := a.memberByID(memberID)
39 if !ok {
40 // The grant is gone and we cannot tell what is left. The plain
41 // sentence is the one that claims less.
42 return false
43 }
44 _, still := a.roleFor(sheetID, m)
45 return still
46}
47
48// revokedTeamNotice names the person the notice is about.
49//
50// Naming them is not decoration: revoking the grant also takes them off
51// the list below, so an unnamed sentence would refer to somebody the
52// owner can no longer see anywhere on the screen.
53//
54// The member ID travels in the query and the address does not, which is
55// the whole reason this reads the row back. An email address in a
56// request line lands in the platform edge's access logs — the same leak
57// the share-link token was moved out of one commit earlier — while an
58// id is already on this screen in every revoke form's hidden field.
59//
60// Checked rather than trusted: the id came from a URL, so the sentence
61// is personalised only for a member this sheet genuinely still admits.
62func (a *App) revokedTeamNotice(r *http.Request, sheetID uint64) string {
63 id, err := strconv.ParseUint(r.URL.Query().Get("who"), 10, 64)
64 if err != nil || id == 0 {
65 return revokedTeamText
66 }
67 m, ok := a.memberByID(id)
68 if !ok || !a.stillReaches(sheetID, m.ID) {
69 return revokedTeamText
70 }
71 return "The explicit permission is removed, but since " + m.Address +
72 " is on the team, they still have access."
73}
74
75// noticeTone is the rastrillo callout tone for a notice code on the
76// share, comments and history pages (shared chrome spec §4). They were
77// all one neutral grey callout, so "Access removed." and "Unknown role."
78// looked the same.
79//
80// - positive: the thing asked for happened.
81// - negative: it did not, and the form needs something different.
82// - warning: it happened, and there is something to know about it.
83// - info: nothing happened, and nothing was wrong.
84func noticeTone(code string) string {
85 switch code {
86 case "shared", "revoked", "link-revoked", "added", "resolved", "restored", "undone":
87 return "positive"
88 case "bad-address", "bad-role", "last-owner", "bad-link-role", "empty", "no-cell", "gone":
89 return "negative"
90 case noticeRevokedTeam, "link":
91 return "warning"
92 }
93 return "info"
94}
95