|
|
|
internal/gates/agents_test.go raw
| 1 | package gates | | 2 | | | 3 | import ( | | 4 | "context" | | 5 | "database/sql" | | 6 | "encoding/json" | | 7 | "strconv" | | 8 | "strings" | | 9 | "testing" | | 10 | | | 11 | "github.com/FelineStateMachine/tinyrelay/internal/community" | | 12 | "github.com/FelineStateMachine/tinyrelay/internal/event" | | 13 | "github.com/FelineStateMachine/tinyrelay/internal/policy" | | 14 | "github.com/FelineStateMachine/tinyrelay/internal/relay" | | 15 | "github.com/FelineStateMachine/tinyrelay/internal/sites" | | 16 | "github.com/FelineStateMachine/tinyrelay/internal/storage" | | 17 | ) | | 18 | | | 19 | const ( | | 20 | agentOwnerSecret = "0000000000000000000000000000000000000000000000000000000000000001" | | 21 | agentSecret = "0000000000000000000000000000000000000000000000000000000000000002" | | 22 | agentOtherSecret = "0000000000000000000000000000000000000000000000000000000000000003" | | 23 | agentNow = int64(1_700_000_000) | | 24 | ) | | 25 | | | 26 | type agentFixture struct { | | 27 | gate *Gate | | 28 | community *community.Service | | 29 | store *storage.Store | | 30 | owner string | | 31 | agent string | | 32 | } | | 33 | | | 34 | func newAgentFixture(t *testing.T) agentFixture { | | 35 | t.Helper() | | 36 | ctx := context.Background() | | 37 | store, err := storage.Open(ctx, t.TempDir()+"/relay.db" ) | | 38 | if err != nil { | | 39 | t.Fatal(err) | | 40 | } | | 41 | t.Cleanup(func() { _ = store.Close() }) | | 42 | owner, _ := event.PublicKey(agentOwnerSecret) | | 43 | agent, _ := event.PublicKey(agentSecret) | | 44 | svc, err := community.New(ctx, store, owner) | | 45 | if err != nil { | | 46 | t.Fatal(err) | | 47 | } | | 48 | p := policy.Defaults(owner) | | 49 | gate, err := New(Config{Store: store, Community: svc, Policy: func() policy.Policy { return p }, Slug: "general" }) | | 50 | if err != nil { | | 51 | t.Fatal(err) | | 52 | } | | 53 | return agentFixture{gate: gate, community: svc, store: store, owner: owner, agent: agent} | | 54 | } | | 55 | | | 56 | func signed(t *testing.T, secret string, kind int, createdAt int64, tags [][]string, content string) event.Event { | | 57 | t.Helper() | | 58 | if tags == nil { | | 59 | tags = [][]string{} | | 60 | } | | 61 | e := event.Event{Kind: kind, CreatedAt: createdAt, Tags: tags, Content: content} | | 62 | if err := event.Sign(&e, secret); err != nil { | | 63 | t.Fatal(err) | | 64 | } | | 65 | return e | | 66 | } | | 67 | | | 68 | func grantTags(agent string, expires int64, extra ...[]string) [][]string { | | 69 | tags := [][]string{{"d" , agent}, {"p" , agent}, {"name" , "helper" }, {"expiration" , strconv.FormatInt(expires, 10)}} | | 70 | return append(tags, extra...) | | 71 | } | | 72 | | | 73 | // grant publishes a grant through the gate and mirrors it as the tenant | | 74 | // would. Later grants for the same agent need a later createdAt to replace | | 75 | // the earlier one. | | 76 | func (f agentFixture) grant(t *testing.T, createdAt int64, tags [][]string) event.Event { | | 77 | t.Helper() | | 78 | ctx := context.Background() | | 79 | e := signed(t, agentOwnerSecret, event.KIND_AGENT_GRANT, createdAt, tags, "" ) | | 80 | if err := f.gate.Write(ctx, e, relay.Session{PubKeys: []string{f.owner}}, agentNow); err != nil { | | 81 | t.Fatalf("grant rejected: %v" , err) | | 82 | } | | 83 | if err := f.store.WithTx(ctx, func(tx *sql.Tx) error { | | 84 | if _, err := storage.SaveTx(ctx, tx, e, storage.SaveOptions{Now: agentNow}); err != nil { | | 85 | return err | | 86 | } | | 87 | return f.community.ApplyAgentEventTx(ctx, tx, e, agentNow) | | 88 | }); err != nil { | | 89 | t.Fatal(err) | | 90 | } | | 91 | return e | | 92 | } | | 93 | | | 94 | func expectRestricted(t *testing.T, err error, fragment string) { | | 95 | t.Helper() | | 96 | if err == nil || !strings.HasPrefix(err.Error(), "restricted: agent grant" ) || !strings.Contains(err.Error(), fragment) { | | 97 | t.Fatalf("want restricted agent grant error containing %q, got %v" , fragment, err) | | 98 | } | | 99 | } | | 100 | | | 101 | func TestAgentGrantScopesKindsRoomsAndRepositories(t *testing.T) { | | 102 | f := newAgentFixture(t) | | 103 | ctx := context.Background() | | 104 | repo := f.owner + ":project" | | 105 | f.grant(t, agentNow, grantTags(f.agent, agentNow+3600, []string{"room" , "general" }, []string{"repo" , repo + ":read" }, []string{"k" , "1" }, []string{"k" , "1621" }, []string{"k" , "1630" }, []string{"k" , "1111" })) | | 106 | if role, _ := f.community.Role(ctx, f.agent); role != "agent" { | | 107 | t.Fatalf("role = %q, want agent" , role) | | 108 | } | | 109 | session := relay.Session{PubKeys: []string{f.agent}} | | 110 | if err := f.gate.Write(ctx, signed(t, agentSecret, 1, agentNow, nil, "hello" ), session, agentNow); err != nil { | | 111 | t.Fatalf("allowed kind rejected: %v" , err) | | 112 | } | | 113 | if err := f.gate.Write(ctx, signed(t, agentSecret, 0, agentNow, nil, "{}" ), session, agentNow); err != nil { | | 114 | t.Fatalf("profile rejected: %v" , err) | | 115 | } | | 116 | expectRestricted(t, f.gate.Write(ctx, signed(t, agentSecret, 7, agentNow, nil, "+" ), session, agentNow), "kind 7" ) | | 117 | if err := f.gate.Write(ctx, signed(t, agentSecret, 1, agentNow, [][]string{{"h" , "general" }}, "in room" ), session, agentNow); err != nil { | | 118 | t.Fatalf("allowed room rejected: %v" , err) | | 119 | } | | 120 | expectRestricted(t, f.gate.Write(ctx, signed(t, agentSecret, 1, agentNow, [][]string{{"h" , "other" }}, "wrong room" ), session, agentNow), "room other" ) | | 121 | address := "30617:" + repo | | 122 | if err := f.gate.Write(ctx, signed(t, agentSecret, 1621, agentNow, [][]string{{"a" , address}}, "issue" ), session, agentNow); err != nil { | | 123 | t.Fatalf("issue in readable repository rejected: %v" , err) | | 124 | } | | 125 | if err := f.gate.Write(ctx, signed(t, agentSecret, 1111, agentNow, [][]string{{"A" , address}}, "comment" ), session, agentNow); err != nil { | | 126 | t.Fatalf("comment in readable repository rejected: %v" , err) | | 127 | } | | 128 | expectRestricted(t, f.gate.Write(ctx, signed(t, agentSecret, 1630, agentNow, [][]string{{"a" , address}}, "status" ), session, agentNow), "status changes" ) | | 129 | expectRestricted(t, f.gate.Write(ctx, signed(t, agentSecret, 1621, agentNow, [][]string{{"a" , "30617:" + f.owner + ":elsewhere" }}, "issue" ), session, agentNow), "repository" ) | | 130 | expectRestricted(t, f.gate.Write(ctx, signed(t, agentSecret, 1621, agentNow, nil, "issue" ), session, agentNow), "repository address" ) | | 131 | f.grant(t, agentNow+1, grantTags(f.agent, agentNow+3600, []string{"repo" , repo + ":maintain" }, []string{"k" , "1630" })) | | 132 | if err := f.gate.Write(ctx, signed(t, agentSecret, 1630, agentNow, [][]string{{"a" , address}}, "status" ), session, agentNow); err != nil { | | 133 | t.Fatalf("maintainer status rejected: %v" , err) | | 134 | } | | 135 | // Propose publishes the same kinds as read; status changes still need | | 136 | // maintain. | | 137 | f.grant(t, agentNow+2, grantTags(f.agent, agentNow+3600, []string{"repo" , repo + ":propose" }, []string{"k" , "1621" }, []string{"k" , "1618" }, []string{"k" , "1617" }, []string{"k" , "1111" }, []string{"k" , "1630" })) | | 138 | for _, kind := range []int{1621, 1618, 1617, 1111} { | | 139 | if err := f.gate.Write(ctx, signed(t, agentSecret, kind, agentNow, [][]string{{"a" , address}}, "proposal" ), session, agentNow); err != nil { | | 140 | t.Fatalf("kind %d in proposable repository rejected: %v" , kind, err) | | 141 | } | | 142 | } | | 143 | expectRestricted(t, f.gate.Write(ctx, signed(t, agentSecret, 1630, agentNow, [][]string{{"a" , address}}, "status" ), session, agentNow), "status changes" ) | | 144 | if _, err := community.ParseAgentGrant(signed(t, agentOwnerSecret, event.KIND_AGENT_GRANT, agentNow, grantTags(f.agent, agentNow+3600, []string{"repo" , repo + ":admin" }), "" ), agentNow); err == nil || !strings.Contains(err.Error(), ":propose, :read or :maintain" ) { | | 145 | t.Fatalf("unknown level accepted: %v" , err) | | 146 | } | | 147 | // Wiki access carries the wiki kinds without listing them. | | 148 | f.grant(t, agentNow+3, grantTags(f.agent, agentNow+3600, []string{"wiki" , "propose" })) | | 149 | for _, kind := range []int{event.KIND_WIKI_ARTICLE, event.KIND_WIKI_MERGE} { | | 150 | if err := f.gate.Write(ctx, signed(t, agentSecret, kind, agentNow, [][]string{{"d" , "notes" }, {"title" , "Notes" }, {"a" , "30818:" + f.owner + ":notes" }, {"p" , f.owner}, {"e" , strings.Repeat("e" , 64), "" , "source" }}, "wiki" ), session, agentNow); err != nil { | | 151 | t.Fatalf("wiki kind %d rejected for propose: %v" , kind, err) | | 152 | } | | 153 | } | | 154 | expectRestricted(t, f.gate.Write(ctx, signed(t, agentSecret, event.KIND_WIKI_REDIRECT, agentNow, [][]string{{"d" , "notes" }}, "" ), session, agentNow), "kind 30819" ) | | 155 | f.grant(t, agentNow+4, grantTags(f.agent, agentNow+3600, []string{"wiki" , "edit" })) | | 156 | if err := f.gate.Write(ctx, signed(t, agentSecret, event.KIND_WIKI_REDIRECT, agentNow, [][]string{{"d" , "notes" }, {"redirect" , "30818:" + f.owner + ":notes" }}, "" ), session, agentNow); err != nil { | | 157 | t.Fatalf("redirect rejected for edit: %v" , err) | | 158 | } | | 159 | } | | 160 | | | 161 | func TestAgentGrantStateRejectsWrites(t *testing.T) { | | 162 | f := newAgentFixture(t) | | 163 | ctx := context.Background() | | 164 | session := relay.Session{PubKeys: []string{f.agent}} | | 165 | note := signed(t, agentSecret, 1, agentNow, nil, "hello" ) | | 166 | f.grant(t, agentNow, grantTags(f.agent, agentNow+60, []string{"k" , "1" })) | | 167 | if err := f.gate.Write(ctx, note, session, agentNow); err != nil { | | 168 | t.Fatalf("active grant rejected: %v" , err) | | 169 | } | | 170 | expectRestricted(t, f.gate.Write(ctx, note, session, agentNow+61), "expired" ) | | 171 | if _, err := f.community.SetAgentPaused(ctx, f.owner, f.agent, true); err != nil { | | 172 | t.Fatal(err) | | 173 | } | | 174 | expectRestricted(t, f.gate.Write(ctx, note, session, agentNow), "paused" ) | | 175 | if _, err := f.community.SetAgentPaused(ctx, f.owner, f.agent, false); err != nil { | | 176 | t.Fatal(err) | | 177 | } | | 178 | if err := f.gate.Write(ctx, note, session, agentNow); err != nil { | | 179 | t.Fatalf("resumed grant rejected: %v" , err) | | 180 | } | | 181 | if _, err := f.community.RevokeAgent(ctx, f.owner, f.agent, agentNow); err != nil { | | 182 | t.Fatal(err) | | 183 | } | | 184 | expectRestricted(t, f.gate.Write(ctx, note, session, agentNow), "revoked" ) | | 185 | if role, _ := f.community.Role(ctx, f.agent); role != "" { | | 186 | t.Fatalf("revoked agent still has role %q" , role) | | 187 | } | | 188 | f.grant(t, agentNow+1, grantTags(f.agent, agentNow-1, []string{"k" , "1" })) | | 189 | expectRestricted(t, f.gate.Write(ctx, note, session, agentNow), "revoked" ) | | 190 | } | | 191 | | | 192 | func TestAgentGrantRateLimitSlidesOverOneMinute(t *testing.T) { | | 193 | f := newAgentFixture(t) | | 194 | ctx := context.Background() | | 195 | session := relay.Session{PubKeys: []string{f.agent}} | | 196 | f.grant(t, agentNow, grantTags(f.agent, agentNow+3600, []string{"k" , "1" }, []string{"rate" , "2" })) | | 197 | for i := 0; i < 2; i++ { | | 198 | if err := f.gate.Write(ctx, signed(t, agentSecret, 1, agentNow+int64(i), nil, "n" ), session, agentNow+int64(i)); err != nil { | | 199 | t.Fatalf("event %d rejected: %v" , i, err) | | 200 | } | | 201 | } | | 202 | expectRestricted(t, f.gate.Write(ctx, signed(t, agentSecret, 1, agentNow+2, nil, "n" ), session, agentNow+2), "2 events per minute" ) | | 203 | if err := f.gate.Write(ctx, signed(t, agentSecret, 1, agentNow+61, nil, "n" ), session, agentNow+61); err != nil { | | 204 | t.Fatalf("event after the window rejected: %v" , err) | | 205 | } | | 206 | } | | 207 | | | 208 | func TestHumanMemberWithGrantKeepsMemberPermissions(t *testing.T) { | | 209 | f := newAgentFixture(t) | | 210 | ctx := context.Background() | | 211 | raw, _ := json.Marshal(f.agent) | | 212 | if _, err := f.community.Execute(ctx, f.owner, "setmember" , []json.RawMessage{raw}); err != nil { | | 213 | t.Fatal(err) | | 214 | } | | 215 | f.grant(t, agentNow, grantTags(f.agent, agentNow+3600, []string{"k" , "1" })) | | 216 | if role, _ := f.community.Role(ctx, f.agent); role != "member" { | | 217 | t.Fatalf("grant changed a human role to %q" , role) | | 218 | } | | 219 | if err := f.gate.Write(ctx, signed(t, agentSecret, 7, agentNow, nil, "+" ), relay.Session{PubKeys: []string{f.agent}}, agentNow); err != nil { | | 220 | t.Fatalf("member constrained by grant: %v" , err) | | 221 | } | | 222 | } | | 223 | | | 224 | func TestImportEnforcesAgentGrant(t *testing.T) { | | 225 | f := newAgentFixture(t) | | 226 | ctx := context.Background() | | 227 | f.grant(t, agentNow, grantTags(f.agent, agentNow+3600, []string{"k" , "1" })) | | 228 | if err := f.gate.Import(ctx, signed(t, agentSecret, 1, agentNow, nil, "synced" ), agentNow); err != nil { | | 229 | t.Fatalf("allowed import rejected: %v" , err) | | 230 | } | | 231 | expectRestricted(t, f.gate.Import(ctx, signed(t, agentSecret, 7, agentNow, nil, "+" ), agentNow), "kind 7" ) | | 232 | } | | 233 | | | 234 | func TestAgentGrantShapeAndSigner(t *testing.T) { | | 235 | f := newAgentFixture(t) | | 236 | ctx := context.Background() | | 237 | other, _ := event.PublicKey(agentOtherSecret) | | 238 | cases := []struct { | | 239 | name string | | 240 | secret string | | 241 | tags [][]string | | 242 | want string | | 243 | }{ | | 244 | {"stranger signer" , agentOtherSecret, grantTags(f.agent, agentNow+60), "restricted: only the owner or a moderator" }, | | 245 | {"missing expiration" , agentOwnerSecret, [][]string{{"d" , f.agent}, {"p" , f.agent}}, "invalid: agent grant needs an expiration" }, | | 246 | {"too far out" , agentOwnerSecret, grantTags(f.agent, agentNow+366*86400), "invalid: agent grant expiration" }, | | 247 | {"mismatched p" , agentOwnerSecret, [][]string{{"d" , f.agent}, {"p" , other}, {"expiration" , "1700003600" }}, "invalid: agent grant p tag" }, | | 248 | {"self grant" , agentOwnerSecret, grantTags(f.owner, agentNow+60), "invalid: agent grant may not name its signer" }, | | 249 | {"bad repo" , agentOwnerSecret, grantTags(f.agent, agentNow+60, []string{"repo" , "nope" }), "invalid: agent grant repo tag" }, | | 250 | {"bad rate" , agentOwnerSecret, grantTags(f.agent, agentNow+60, []string{"rate" , "601" }), "invalid: agent grant rate" }, | | 251 | {"bad wiki" , agentOwnerSecret, grantTags(f.agent, agentNow+60, []string{"wiki" , "admin" }), "invalid: agent grant wiki tag" }, | | 252 | } | | 253 | for _, tc := range cases { | | 254 | t.Run(tc.name, func(t *testing.T) { | | 255 | e := signed(t, tc.secret, event.KIND_AGENT_GRANT, agentNow, tc.tags, "" ) | | 256 | err := f.gate.Write(ctx, e, relay.Session{PubKeys: []string{e.PubKey}}, agentNow) | | 257 | if err == nil || !strings.HasPrefix(err.Error(), tc.want) { | | 258 | t.Fatalf("want %q, got %v" , tc.want, err) | | 259 | } | | 260 | }) | | 261 | } | | 262 | e := signed(t, agentOwnerSecret, event.KIND_AGENT_GRANT, agentNow, grantTags(f.agent, agentNow+60, []string{"k" , "1" }, []string{"wiki" , "propose" }), `{"note":"ok"}` ) | | 263 | if err := f.gate.Write(ctx, e, relay.Session{PubKeys: []string{f.owner}}, agentNow); err != nil { | | 264 | t.Fatalf("valid grant rejected: %v" , err) | | 265 | } | | 266 | grant, err := community.ParseAgentGrant(e, agentNow) | | 267 | if err != nil || grant.Scope.Rate != community.AgentRateDefault || grant.Scope.Wiki != "propose" || grant.Name != "helper" { | | 268 | t.Fatalf("parsed grant %+v %v" , grant, err) | | 269 | } | | 270 | } | | 271 | | | 272 | func TestAgentGrantSitesCoverLabelsAndRequireExpiry(t *testing.T) { | | 273 | f := newAgentFixture(t) | | 274 | ctx := context.Background() | | 275 | session := relay.Session{PubKeys: []string{f.agent}} | | 276 | hash := strings.Repeat("a" , 64) | | 277 | paths := [][]string{{"path" , "/index.html" , hash}} | | 278 | own := sites.SiteLabel(event.Event{Kind: sites.KindSite, PubKey: f.agent}) | | 279 | base, _ := sites.Base36(f.agent) | | 280 | named := base + "docs" | | 281 | manifest := func(kind int, tags ...[]string) event.Event { | | 282 | return signed(t, agentSecret, kind, agentNow, append(append([][]string{}, paths...), tags...), "" ) | | 283 | } | | 284 | // Without a sites tag the manifest kinds stay closed. | | 285 | f.grant(t, agentNow, grantTags(f.agent, agentNow+3600, []string{"k" , "1" })) | | 286 | expectRestricted(t, f.gate.Write(ctx, manifest(sites.KindSite), session, agentNow), "kind 15128" ) | | 287 | // One label without a ttl: that site only, no expiration needed. | | 288 | f.grant(t, agentNow+1, grantTags(f.agent, agentNow+3600, []string{"sites" , named})) | | 289 | if err := f.gate.Write(ctx, manifest(sites.KindNamedSite, []string{"d" , "docs" }), session, agentNow); err != nil { | | 290 | t.Fatalf("covered named site rejected: %v" , err) | | 291 | } | | 292 | expectRestricted(t, f.gate.Write(ctx, manifest(sites.KindNamedSite, []string{"d" , "blog" }), session, agentNow), "site " +base+"blog" ) | | 293 | expectRestricted(t, f.gate.Write(ctx, manifest(sites.KindSite), session, agentNow), "site " +own) | | 294 | // A ttl requires an expiration tag within it; the reason says what to add. | | 295 | f.grant(t, agentNow+2, grantTags(f.agent, agentNow+3600, []string{"sites" , own, "ttl=2" }, []string{"sites" , named, "ttl=1" , "encrypted" })) | | 296 | latest := strconv.FormatInt(agentNow+2*86400, 10) | | 297 | err := f.gate.Write(ctx, manifest(sites.KindSite), session, agentNow) | | 298 | if err == nil || !strings.HasPrefix(err.Error(), "invalid: agent grant allows site " +own+" for 2 days: add an expiration tag no later than " +latest) { | | 299 | t.Fatalf("manifest without expiration: %v" , err) | | 300 | } | | 301 | err = f.gate.Write(ctx, manifest(sites.KindSite, []string{"expiration" , strconv.FormatInt(agentNow+3*86400, 10)}), session, agentNow) | | 302 | if err == nil || !strings.Contains(err.Error(), "set the expiration tag no later than " +latest) { | | 303 | t.Fatalf("manifest expiring too late: %v" , err) | | 304 | } | | 305 | if err := f.gate.Write(ctx, manifest(sites.KindSite, []string{"expiration" , latest}), session, agentNow); err != nil { | | 306 | t.Fatalf("manifest within the ttl rejected: %v" , err) | | 307 | } | | 308 | if err := f.gate.Write(ctx, manifest(sites.KindNamedSite, []string{"d" , "docs" }, []string{"expiration" , strconv.FormatInt(agentNow+86400, 10)}), session, agentNow); err != nil { | | 309 | t.Fatalf("named manifest within its own ttl rejected: %v" , err) | | 310 | } | | 311 | if err := f.gate.Write(ctx, manifest(sites.KindNamedSite, []string{"d" , "docs" }, []string{"expiration" , latest}), session, agentNow); err == nil { | | 312 | t.Fatal("named manifest beyond its own ttl accepted" ) | | 313 | } | | 314 | // A star covers every label under the key and the longest ttl applies. | | 315 | grant := f.grant(t, agentNow+3, grantTags(f.agent, agentNow+3600, []string{"sites" , "*" , "ttl=5" }, []string{"sites" , named, "ttl=1" })) | | 316 | if err := f.gate.Write(ctx, manifest(sites.KindNamedSite, []string{"d" , "blog" }, []string{"expiration" , strconv.FormatInt(agentNow+5*86400, 10)}), session, agentNow); err != nil { | | 317 | t.Fatalf("star label rejected: %v" , err) | | 318 | } | | 319 | if err := f.gate.Write(ctx, manifest(sites.KindNamedSite, []string{"d" , "docs" }, []string{"expiration" , strconv.FormatInt(agentNow+5*86400, 10)}), session, agentNow); err != nil { | | 320 | t.Fatalf("longest ttl not applied: %v" , err) | | 321 | } | | 322 | parsed, err := community.ParseAgentGrant(grant, agentNow) | | 323 | if err != nil || parsed.UploadTTLDays() != 5 || parsed.UploadsEncrypted() { | | 324 | t.Fatalf("parsed sites %+v %v" , parsed.Scope.Sites, err) | | 325 | } | | 326 | // Snapshots are not admitted by the sites tag. | | 327 | expectRestricted(t, f.gate.Write(ctx, manifest(sites.KindSiteSnapshot), session, agentNow), "kind 5128" ) | | 328 | // Paused and revoked grants stop manifests like every other event. | | 329 | if _, err := f.community.SetAgentPaused(ctx, f.owner, f.agent, true); err != nil { | | 330 | t.Fatal(err) | | 331 | } | | 332 | expectRestricted(t, f.gate.Write(ctx, manifest(sites.KindSite, []string{"expiration" , latest}), session, agentNow), "paused" ) | | 333 | if _, err := f.community.RevokeAgent(ctx, f.owner, f.agent, agentNow); err != nil { | | 334 | t.Fatal(err) | | 335 | } | | 336 | expectRestricted(t, f.gate.Write(ctx, manifest(sites.KindSite, []string{"expiration" , latest}), session, agentNow), "revoked" ) | | 337 | } | | 338 | | | 339 | func TestAgentGrantSitesShape(t *testing.T) { | | 340 | f := newAgentFixture(t) | | 341 | ctx := context.Background() | | 342 | other, _ := event.PublicKey(agentOtherSecret) | | 343 | own := sites.SiteLabel(event.Event{Kind: sites.KindSite, PubKey: f.agent}) | | 344 | foreign := sites.SiteLabel(event.Event{Kind: sites.KindSite, PubKey: other}) | | 345 | for _, tc := range []struct { | | 346 | name string | | 347 | tag []string | | 348 | want string | | 349 | }{ | | 350 | {"empty" , []string{"sites" }, "invalid: agent grant sites tag needs a site label" }, | | 351 | {"bad label" , []string{"sites" , "not-a-site" }, "invalid: agent grant sites tag needs a site label" }, | | 352 | {"other key" , []string{"sites" , foreign}, "invalid: agent grant sites label must be a site under the agent's own key" }, | | 353 | {"snapshot" , []string{"sites" , "v" + strings.Repeat("0" , 50)}, "invalid: agent grant sites tag needs a site label" }, | | 354 | {"ttl zero" , []string{"sites" , "*" , "ttl=0" }, "invalid: agent grant sites ttl must be between 1 and 365" }, | | 355 | {"ttl long" , []string{"sites" , "*" , "ttl=366" }, "invalid: agent grant sites ttl must be between 1 and 365" }, | | 356 | {"ttl text" , []string{"sites" , "*" , "ttl=soon" }, "invalid: agent grant sites ttl must be between 1 and 365" }, | | 357 | {"unknown flag" , []string{"sites" , own, "public" }, "invalid: agent grant sites tag allows only ttl=<days> and encrypted" }, | | 358 | } { | | 359 | t.Run(tc.name, func(t *testing.T) { | | 360 | e := signed(t, agentOwnerSecret, event.KIND_AGENT_GRANT, agentNow, grantTags(f.agent, agentNow+60, tc.tag), "" ) | | 361 | err := f.gate.Write(ctx, e, relay.Session{PubKeys: []string{f.owner}}, agentNow) | | 362 | if err == nil || !strings.HasPrefix(err.Error(), tc.want) { | | 363 | t.Fatalf("want %q, got %v" , tc.want, err) | | 364 | } | | 365 | }) | | 366 | } | | 367 | e := signed(t, agentOwnerSecret, event.KIND_AGENT_GRANT, agentNow, grantTags(f.agent, agentNow+60, []string{"sites" , own, "ttl=30" , "encrypted" }, []string{"sites" , own, "ttl=7" }, []string{"sites" , "*" }), "" ) | | 368 | if err := f.gate.Write(ctx, e, relay.Session{PubKeys: []string{f.owner}}, agentNow); err != nil { | | 369 | t.Fatalf("valid sites grant rejected: %v" , err) | | 370 | } | | 371 | grant, err := community.ParseAgentGrant(e, agentNow) | | 372 | if err != nil { | | 373 | t.Fatal(err) | | 374 | } | | 375 | want := []community.AgentSite{{Label: own, TTLDays: 30, Encrypted: true}, {Label: "*" }} | | 376 | if len(grant.Scope.Sites) != len(want) || grant.Scope.Sites[0] != want[0] || grant.Scope.Sites[1] != want[1] { | | 377 | t.Fatalf("sites = %+v, want %+v" , grant.Scope.Sites, want) | | 378 | } | | 379 | if !grant.UploadsEncrypted() || grant.UploadTTLDays() != 0 { | | 380 | t.Fatalf("an entry without a ttl should leave uploads unlimited: ttl=%d encrypted=%v" , grant.UploadTTLDays(), grant.UploadsEncrypted()) | | 381 | } | | 382 | } |
|
|
About| owner | c01503e8035f |
|---|
| visibility | public |
|---|
| head | d730679639bf |
|---|
Maintainers- c01503e8035f owner
- ba1b5beed5b5 agent hermes
Clonehttps://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git
|