policies.gno
3.67 Kb · 127 lines
1package memba_notes_v1
2
3import (
4 "gno.land/p/samcrew/avl"
5 "strconv"
6 "strings"
7)
8
9const (
10 OpRead uint8 = iota
11 OpWrite
12 OpComment
13)
14
15// The realm parameter forces an explicit crossing: adapters run with their own
16// realm identity. All mutators retain the busy guard throughout callbacks.
17type Policy interface {
18 Allows(cur realm, id [16]byte, who address, op uint8) bool
19}
20
21var policies = avl.NewTree()
22
23func RegisterPolicy(cur realm, slug string, p Policy) string {
24 enter(cur, false)
25 defer leave()
26 path := cur.Previous().PkgPath()
27 if !strings.HasPrefix(path, "gno.land/r/") || len(path) > 200 || len(slug) < 1 || len(slug) > 32 || p == nil {
28 panic("notes: realm policy required")
29 }
30 for _, c := range slug {
31 if !(c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '-') {
32 panic("notes: invalid policy slug")
33 }
34 }
35 key := path + ":" + slug
36 if _, ok := policies.Get(key); ok {
37 panic("notes: policy already registered")
38 }
39 policies.Set(key, p)
40 return key
41}
42func SetPolicy(cur realm, id [16]byte, baseRevision uint64, op uint8, key string, opID [16]byte) {
43 who := enter(cur, false)
44 defer leave()
45 mutateSetPolicy(cur, who, id, baseRevision, op, key, opID)
46}
47func mutateSetPolicy(cur realm, who address, id [16]byte, baseRevision uint64, op uint8, key string, opID [16]byte) {
48 n := live(id)
49 owner(n, who)
50 expected(n, baseRevision)
51 if op > OpComment {
52 panic("notes: unknown policy operation")
53 }
54 if key != "" {
55 if _, ok := policies.Get(key); !ok {
56 panic("notes: policy missing")
57 }
58 }
59 operation(n, opID)
60 n.Policies[op] = key
61 bumpHistory(n, who, opID, "policy", `,"policy_op":`+strconv.Itoa(int(op))+`,"policy_key":`+q(key))
62}
63func policyAllows(cur realm, n *note, who address, op uint8) bool {
64 key := n.Policies[op]
65 if key == "" {
66 return false
67 }
68 p, ok := policies.Get(key)
69 if !ok {
70 panic("notes: missing stored policy")
71 }
72 return p.(Policy).Allows(cross(cur), n.ID, who, op)
73}
74func PoliciesJSON(idHex string) string {
75 n := find(readID(idHex))
76 return `{"read":` + q(n.Policies[0]) + `,"write":` + q(n.Policies[1]) + `,"comment":` + q(n.Policies[2]) + `}`
77}
78
79// A holder may grant a read-policy-approved recipient the current epoch. Every
80// existing entry must remain byte-identical: a grantee cannot corrupt someone
81// else's wrap, change writers, remove access or rotate the owner into new keys.
82func GrantAccess(cur realm, id [16]byte, baseRevision uint64, expectedEpoch uint32, manifest []byte, opID [16]byte) {
83 who := enter(cur, false)
84 defer leave()
85 n := live(id)
86 expected(n, baseRevision)
87 privateWritable(n)
88 if n.Mode != ModeShared || expectedEpoch != n.Epoch || !contains(n.Readers, who) {
89 panic("notes: current shared reader required")
90 }
91 list := parseManifest(manifest, n.Epoch, manifestOwner(n), n.Mode)
92 old := getEpoch(id, n.Epoch).Manifest
93 for i := 0; i < int(old[1]); i++ {
94 entry := old[2+i*ManifestEntryBytes : 2+(i+1)*ManifestEntryBytes]
95 found := false
96 for j := 0; j < int(manifest[1]); j++ {
97 next := manifest[2+j*ManifestEntryBytes : 2+(j+1)*ManifestEntryBytes]
98 if string(entry) == string(next) {
99 found = true
100 break
101 }
102 }
103 if !found {
104 panic("notes: existing wrap immutable for reader")
105 }
106 }
107 added := false
108 for _, r := range list {
109 if !contains(n.Readers, r.Who) {
110 govGrant := n.GovWriters && n.Owner == govAddress && govMember(who) && govMember(r.Who)
111 if r.Writer || who != n.Owner && !govGrant && !policyAllows(cur, n, r.Who, OpRead) {
112 panic("notes: read policy denied")
113 }
114 added = true
115 }
116 }
117 if !added {
118 panic("notes: no new reader")
119 }
120 operation(n, opID)
121 unindex(n)
122 setRecipients(n, list)
123 index(n)
124 getEpoch(id, n.Epoch).Manifest = copied(manifest)
125 acknowledgeRequests(n, who, opID)
126 bump(n, who, opID, "read-grant")
127}