Search Apps Documentation Source Content File Folder Download Copy Actions Download 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}