tinyrelayfile
tinyrelay/internal/daemon/grant_admission_test.go/

internal/daemon/grant_admission_test.go raw
1package daemon
2
3import (
4 "context"
5 "encoding/json"
6 "strconv"
7 "strings"
8 "testing"
9 "time"
10
11 "github.com/FelineStateMachine/tinyrelay/internal/community"
12 "github.com/FelineStateMachine/tinyrelay/internal/event"
13)
14
15func grantRequestEvent(t *testing.T, secret string, grant event.Event, at int64, content, grantJSON string, tags ...[]string) event.Event {
16 t.Helper()
17 base := [][]string{{"request", "grant"}, {"grant", grantJSON}, {"E", grant.ID, "", grant.PubKey}, {"K", "30392"}, {"P", grant.PubKey}, {"e", grant.ID, "", grant.PubKey}, {"k", "30392"}, {"p", grant.PubKey}}
18 return signedEvent(t, secret, 1111, at, append(base, tags...), content)
19}
20
21func grantRequestJSON(t *testing.T, grant event.Event, changes community.AgentGrantChanges) string {
22 t.Helper()
23 raw, err := json.Marshal(community.AgentGrantRequest{Base: grant.ID, Changes: changes})
24 if err != nil {
25 t.Fatal(err)
26 }
27 return string(raw)
28}
29
30func setGrantTag(t *testing.T, e *event.Event, name, value string) {
31 t.Helper()
32 for _, tag := range e.Tags {
33 if len(tag) > 1 && tag[0] == name {
34 tag[1] = value
35 return
36 }
37 }
38 t.Fatalf("grant replacement has no %s tag", name)
39}
40
41func TestGrantRequestAdmissionRequiresAgentAndValidCurrentGrant(t *testing.T) {
42 _, tenant := testTenant(t)
43 now := time.Now().Unix()
44 agent, _ := event.PublicKey(testAgentSecret)
45 member, _ := event.PublicKey(testMemberSecret)
46 grant := agentGrantEvent(t, agent, now-30, now+3600, []string{"k", "9"}, []string{"k", "1111"})
47 if err := publishAs(t, tenant, grant); err != nil {
48 t.Fatal(err)
49 }
50 reqJSON := grantRequestJSON(t, grant, community.AgentGrantChanges{Kinds: []int{11}})
51 human := grantRequestEvent(t, testMemberSecret, grant, now, "please", reqJSON)
52 if err := publishAs(t, tenant, human); err == nil {
53 t.Fatal("human request accepted")
54 }
55
56 for name, mutate := range map[string]func(event.Event) event.Event{
57 "rationale over limit": func(e event.Event) event.Event { e.Content = strings.Repeat("x", 501); return e },
58 "rationale whitespace": func(e event.Event) event.Event { e.Content = " \t\n "; return e },
59 "duplicate operator": func(e event.Event) event.Event { e.Tags = append(e.Tags, []string{"p", grant.PubKey}); return e },
60 "wrong operator": func(e event.Event) event.Event { e.Tags[4][1] = member; e.Tags[7][1] = member; return e },
61 "wrong base": func(e event.Event) event.Event {
62 e.Tags[2][1] = strings.Repeat("f", 64)
63 e.Tags[5][1] = strings.Repeat("f", 64)
64 return e
65 },
66 "unknown change": func(e event.Event) event.Event {
67 e.Tags[1][1] = `{"base":"` + grant.ID + `","changes":{"nope":true}}`
68 return e
69 },
70 "noop": func(e event.Event) event.Event {
71 e.Tags[1][1] = grantRequestJSON(t, grant, community.AgentGrantChanges{Kinds: []int{9}})
72 return e
73 },
74 } {
75 t.Run(name, func(t *testing.T) {
76 e := grantRequestEvent(t, testAgentSecret, grant, now+1, "please", reqJSON)
77 e = mutate(e)
78 if err := event.Sign(&e, testAgentSecret); err != nil {
79 t.Fatal(err)
80 }
81 if err := publishAs(t, tenant, e); err == nil {
82 t.Fatalf("malformed request accepted: %v", e)
83 }
84 })
85 }
86}
87
88func TestGrantRequestAdmissionRejectsPausedAndRevokedAgents(t *testing.T) {
89 for _, action := range []string{"pauseagent", "revokeagent"} {
90 t.Run(action, func(t *testing.T) {
91 _, tenant := testTenant(t)
92 now := time.Now().Unix()
93 agent, _ := event.PublicKey(testAgentSecret)
94 grant := agentGrantEvent(t, agent, now-30, now+3600, []string{"k", "9"})
95 if err := publishAs(t, tenant, grant); err != nil {
96 t.Fatal(err)
97 }
98 args := []json.RawMessage{json.RawMessage(`"` + agent + `"`)}
99 if _, err := tenant.Execute(context.Background(), tenant.Policy().Owner, action, args); err != nil {
100 t.Fatal(err)
101 }
102 req := grantRequestEvent(t, testAgentSecret, grant, now+1, "please", grantRequestJSON(t, grant, community.AgentGrantChanges{Kinds: []int{11}}))
103 if err := publishAs(t, tenant, req); err == nil {
104 t.Fatalf("request accepted after %s", action)
105 }
106 })
107 }
108}
109
110func TestGrantRequestReplacementMustMatchPreparedReview(t *testing.T) {
111 mutations := map[string]func(*event.Event){
112 "expiry": func(e *event.Event) { setGrantTag(t, e, "expiration", "9999999999") },
113 "name": func(e *event.Event) { setGrantTag(t, e, "name", "changed") },
114 "content": func(e *event.Event) { e.Content = "changed" },
115 "tags": func(e *event.Event) { e.Tags = append(e.Tags, []string{"note", "changed"}) },
116 }
117 for name, mutate := range mutations {
118 t.Run(name, func(t *testing.T) {
119 _, tenant := testTenant(t)
120 now := time.Now().Unix()
121 agent, _ := event.PublicKey(testAgentSecret)
122 grant := agentGrantEvent(t, agent, now-30, now+3600, []string{"k", "9"})
123 if err := publishAs(t, tenant, grant); err != nil {
124 t.Fatal(err)
125 }
126 req := grantRequestEvent(t, testAgentSecret, grant, now, "please", grantRequestJSON(t, grant, community.AgentGrantChanges{Kinds: []int{11}}))
127 if err := publishAs(t, tenant, req); err != nil {
128 t.Fatal(err)
129 }
130 review, err := tenant.community.ReviewAgentGrantRequest(context.Background(), req, now)
131 if err != nil {
132 t.Fatal(err)
133 }
134 replacement := review.Unsigned
135 mutate(&replacement)
136 if err := event.Sign(&replacement, testOwnerSecret); err != nil {
137 t.Fatal(err)
138 }
139 if err := publishAs(t, tenant, replacement); err == nil {
140 t.Fatalf("mutated %s replacement accepted", name)
141 }
142 })
143 }
144}
145
146func TestGrantRequestPreparedReplacementGoesStale(t *testing.T) {
147 for _, scenario := range []string{"pause", "revoke", "deny", "expire"} {
148 t.Run(scenario, func(t *testing.T) {
149 _, tenant := testTenant(t)
150 now := time.Now().Unix()
151 agent, _ := event.PublicKey(testAgentSecret)
152 grant := agentGrantEvent(t, agent, now-30, now+3600, []string{"k", "9"})
153 if err := publishAs(t, tenant, grant); err != nil {
154 t.Fatal(err)
155 }
156 tags := grantRequestJSON(t, grant, community.AgentGrantChanges{Kinds: []int{11}})
157 requestTags := [][]string(nil)
158 if scenario == "expire" {
159 requestTags = [][]string{{"expiration", strconv.FormatInt(now+1, 10)}}
160 }
161 request := grantRequestEvent(t, testAgentSecret, grant, now, "please", tags, requestTags...)
162 if err := publishAs(t, tenant, request); err != nil {
163 t.Fatal(err)
164 }
165 review, err := tenant.community.ReviewAgentGrantRequest(context.Background(), request, now)
166 if err != nil {
167 t.Fatal(err)
168 }
169 replacement := review.Unsigned
170 if scenario == "deny" {
171 if err := publishAs(t, tenant, signedEvent(t, testOwnerSecret, 7, now+1, [][]string{{"e", request.ID}, {"p", agent}}, "-")); err != nil {
172 t.Fatal(err)
173 }
174 } else if scenario != "expire" {
175 action := "pauseagent"
176 if scenario == "revoke" {
177 action = "revokeagent"
178 }
179 if _, err := tenant.Execute(context.Background(), tenant.Policy().Owner, action, []json.RawMessage{json.RawMessage(`"` + agent + `"`)}); err != nil {
180 t.Fatal(err)
181 }
182 }
183 if err := event.Sign(&replacement, testOwnerSecret); err != nil {
184 t.Fatal(err)
185 }
186 if scenario == "expire" {
187 time.Sleep(2 * time.Second)
188 }
189 if err := publishAs(t, tenant, replacement); err == nil {
190 t.Fatalf("stale %s replacement accepted", scenario)
191 }
192 })
193 }
194}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs