oficina / docs Public

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

Plain git — no account needed to clone.

Download

Download this file

1package docs
2
3import (
4 "context"
5 "errors"
6 "testing"
7)
8
9// The write path has to authorize the *actor*, not the document.
10//
11// Before sharing, ApplyUpdate verified `owned(db, doc.OwnerID).Where("id = ?",
12// doc.ID)` — the document against its own owner, which is true of every
13// document by construction. That was harmless while the only way to hold a
14// *Document was to have fetched it through an owner-scoped query, so the
15// check was a belt on top of the read scope's braces. The moment reads accept
16// grantees it stops being harmless: a reader who can fetch the document can
17// satisfy a check that only ever asked whether the document is its own
18// owner's.
19//
20// store.go's own comment records this class of bug being fixed twice already.
21// This is the third, caught before it shipped rather than after.
22func TestApplyUpdateRefusesAReader(t *testing.T) {
23 app, err := NewForTest(t)
24 if err != nil {
25 t.Fatalf("NewForTest: %v", err)
26 }
27 ctx := context.Background()
28 store := app.Store()
29
30 alice, _ := store.EnsurePerson(ctx, "alice@example.com")
31 bob, _ := store.EnsurePerson(ctx, "bob@example.com")
32
33 doc, err := store.Create(ctx, alice, "Alice's document")
34 if err != nil {
35 t.Fatalf("Create: %v", err)
36 }
37 if err := store.Grant(ctx, alice, doc.Sqid, "bob@example.com", RoleReader); err != nil {
38 t.Fatalf("Grant: %v", err)
39 }
40
41 // Bob can read it — that is what the grant is for.
42 var rows []*Document
43 if err := readable(store.db.WithContext(ctx), localActor(bob, "bob@example.com")).Find(&rows).Error; err != nil {
44 t.Fatalf("readable(bob): %v", err)
45 }
46 if len(rows) != 1 {
47 t.Fatalf("bob cannot read the document he was granted; the rest of this test proves nothing")
48 }
49
50 // And he cannot write it.
51 _, err = store.ApplyUpdate(ctx, doc, localActor(bob, "bob@example.com"), []byte{0})
52 if !errors.Is(err, ErrNotWritable) {
53 t.Errorf("ApplyUpdate as a reader: got %v, want ErrNotWritable", err)
54 }
55}
56
57// A writer grant does let them write, or the role is decoration.
58func TestApplyUpdateAllowsAWriter(t *testing.T) {
59 app, err := NewForTest(t)
60 if err != nil {
61 t.Fatalf("NewForTest: %v", err)
62 }
63 ctx := context.Background()
64 store := app.Store()
65
66 alice, _ := store.EnsurePerson(ctx, "alice@example.com")
67 bob, _ := store.EnsurePerson(ctx, "bob@example.com")
68
69 doc, err := store.Create(ctx, alice, "Alice's document")
70 if err != nil {
71 t.Fatalf("Create: %v", err)
72 }
73 if err := store.Grant(ctx, alice, doc.Sqid, "bob@example.com", RoleWriter); err != nil {
74 t.Fatalf("Grant: %v", err)
75 }
76
77 // A malformed CRDT update is fine here: it must get *past* authorization
78 // and fail on its own merits, which is what distinguishes "refused" from
79 // "not a valid update".
80 _, err = store.ApplyUpdate(ctx, doc, localActor(bob, "bob@example.com"), []byte{0})
81 if errors.Is(err, ErrNotWritable) {
82 t.Errorf("ApplyUpdate as a writer was refused authorization; the grant's role is doing nothing")
83 }
84}
85
86// The owner keeps writing without any grant to themselves.
87func TestApplyUpdateAllowsTheOwner(t *testing.T) {
88 app, err := NewForTest(t)
89 if err != nil {
90 t.Fatalf("NewForTest: %v", err)
91 }
92 ctx := context.Background()
93 store := app.Store()
94
95 alice, _ := store.EnsurePerson(ctx, "alice@example.com")
96 doc, err := store.Create(ctx, alice, "Alice's document")
97 if err != nil {
98 t.Fatalf("Create: %v", err)
99 }
100
101 _, err = store.ApplyUpdate(ctx, doc, localActor(alice, "alice@example.com"), []byte{0})
102 if errors.Is(err, ErrNotWritable) {
103 t.Errorf("the owner was refused a write to her own document: %v", err)
104 }
105}
106
107// A stranger holding neither ownership nor a grant is refused, and refused the
108// same way a reader is — the write path does not tell them which.
109func TestApplyUpdateRefusesAStranger(t *testing.T) {
110 app, err := NewForTest(t)
111 if err != nil {
112 t.Fatalf("NewForTest: %v", err)
113 }
114 ctx := context.Background()
115 store := app.Store()
116
117 alice, _ := store.EnsurePerson(ctx, "alice@example.com")
118 carol, _ := store.EnsurePerson(ctx, "carol@example.com")
119 doc, err := store.Create(ctx, alice, "Alice's document")
120 if err != nil {
121 t.Fatalf("Create: %v", err)
122 }
123
124 _, err = store.ApplyUpdate(ctx, doc, localActor(carol, "carol@example.com"), []byte{0})
125 if !errors.Is(err, ErrNotWritable) {
126 t.Errorf("ApplyUpdate as a stranger: got %v, want ErrNotWritable", err)
127 }
128}
129