Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}