1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
package game
// Regression tests for the security-hardening stage:
// - swapid swaps the two room files through a journal transaction and leaves
// no transient temp file behind
// - the admin privilege check re-reads the on-disk flag instead of trusting
// the cached sess.Account.Admin copy
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestSwapIDSwapsRoomFilesTransactionally(t *testing.T) {
h := newHarness(t)
h.seedAccount("admin")
h.seedCharacter("admin", "admin")
acc, err := h.g.AccountStore.LoadAccount("admin")
if err != nil {
t.Fatal(err)
}
acc.Admin = true
if err := h.g.AccountStore.SaveAccount(acc); err != nil {
t.Fatal(err)
}
sess, _ := h.connect("admin")
h.g.RunOnOwner(func() { h.g.executeSwapID(sess, []string{"1", "2"}, "swapid 1 2") })
r1, err := os.ReadFile(filepath.Join(h.dir, "rooms", "1.yaml"))
if err != nil {
t.Fatal(err)
}
r2, err := os.ReadFile(filepath.Join(h.dir, "rooms", "2.yaml"))
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(r1), "name: North") {
t.Fatalf("room 1 not swapped:\n%s", r1)
}
if !strings.Contains(string(r2), "name: Central") {
t.Fatalf("room 2 not swapped:\n%s", r2)
}
// The old implementation renamed through rooms/<id>.yaml.swapid-tmp; the
// transaction-based swap must not leave one behind.
entries, err := os.ReadDir(filepath.Join(h.dir, "rooms"))
if err != nil {
t.Fatal(err)
}
for _, e := range entries {
if strings.Contains(e.Name(), "swapid-tmp") {
t.Fatalf("leftover swap temp file: %s", e.Name())
}
}
// A committed transaction removes its journal.
if jentries, err := os.ReadDir(filepath.Join(h.dir, ".journal")); err == nil {
for _, e := range jentries {
t.Fatalf("leftover journal after successful swap: %s", e.Name())
}
}
}
func TestCheckAdminReReadsDiskFlag(t *testing.T) {
h := newHarness(t)
h.seedAccount("alice")
h.seedCharacter("alice", "alice")
acc, err := h.g.AccountStore.LoadAccount("alice")
if err != nil {
t.Fatal(err)
}
acc.Admin = true
if err := h.g.AccountStore.SaveAccount(acc); err != nil {
t.Fatal(err)
}
sess, _ := h.connect("alice")
var got bool
h.g.RunOnOwner(func() { got = h.g.checkAdmin(sess) })
if !got {
t.Fatal("admin session not recognized")
}
// Revoke admin on disk; the cached session must no longer be privileged.
revoked, err := h.g.AccountStore.LoadAccount("alice")
if err != nil {
t.Fatal(err)
}
revoked.Admin = false
if err := h.g.AccountStore.SaveAccount(revoked); err != nil {
t.Fatal(err)
}
h.g.RunOnOwner(func() { got = h.g.checkAdmin(sess) })
if got {
t.Fatal("stale cached admin flag was trusted after disk revocation")
}
}
|