this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

basic permission-set support in lexicon package (#1150)

I should probably add parsing helpers to the atproto/syntax package for
MIME type glob patterns, and for "service refs".

This isn't super rigorous yet, and it doesn't support pass-through of
extra fields on `permission` types. But it would catch early/initial
syntax issues, and could be used with `goat` for publishing and
resolving lexicons (eg, basic devex for permission-sets feature).

authored by

bnewbold and committed by
GitHub
b7ac8254 a8865584

+179 -1
+1 -1
atproto/lexicon/catalog.go
··· 65 65 } 66 66 // "A file can have at most one definition with one of the "primary" types. Primary types should always have the name main. It is possible for main to describe a non-primary type." 67 67 switch s := def.Inner.(type) { 68 - case SchemaRecord, SchemaQuery, SchemaProcedure, SchemaSubscription: 68 + case SchemaRecord, SchemaQuery, SchemaProcedure, SchemaSubscription, SchemaPermissionSet: 69 69 if frag != "main" { 70 70 return fmt.Errorf("record, query, procedure, and subscription types must be 'main', not: %s", frag) 71 71 }
+120
atproto/lexicon/language.go
··· 36 36 return v.CheckSchema() 37 37 case SchemaSubscription: 38 38 return v.CheckSchema() 39 + case SchemaPermissionSet: 40 + return v.CheckSchema() 41 + case SchemaPermission: 42 + return v.CheckSchema() 39 43 case SchemaNull: 40 44 return v.CheckSchema() 41 45 case SchemaBoolean: ··· 178 182 return nil 179 183 case "subscription": 180 184 v := new(SchemaSubscription) 185 + if err = json.Unmarshal(b, v); err != nil { 186 + return err 187 + } 188 + s.Inner = *v 189 + return nil 190 + case "permission-set": 191 + v := new(SchemaPermissionSet) 192 + if err = json.Unmarshal(b, v); err != nil { 193 + return err 194 + } 195 + s.Inner = *v 196 + return nil 197 + case "permission": 198 + v := new(SchemaPermission) 181 199 if err = json.Unmarshal(b, v); err != nil { 182 200 return err 183 201 } ··· 371 389 return s.Parameters.CheckSchema() 372 390 } 373 391 392 + type SchemaPermissionSet struct { 393 + Type string `json:"type"` // "permission-set" 394 + Description *string `json:"description,omitempty"` 395 + Permissions []SchemaPermission `json:"permissions"` 396 + } 397 + 398 + func (s *SchemaPermissionSet) CheckSchema() error { 399 + for _, p := range s.Permissions { 400 + if err := p.CheckSchema(); err != nil { 401 + return err 402 + } 403 + } 404 + return nil 405 + } 406 + 407 + type SchemaPermission struct { 408 + Type string `json:"type"` // "permission" 409 + Description *string `json:"description,omitempty"` 410 + 411 + Resource string `json:"resource"` 412 + Accept []string `json:"accept,omitempty"` 413 + Collection []string `json:"collection,omitempty"` 414 + Action []string `json:"action,omitempty"` 415 + LXM []string `json:"lxm,omitempty"` 416 + Audience string `json:"aud,omitempty"` 417 + InheritAud bool `json:"inheritAud,omitempty"` 418 + } 419 + 420 + func (s *SchemaPermission) CheckSchema() error { 421 + if s.Type != "permission" { 422 + return fmt.Errorf("expected 'permission'") 423 + } 424 + switch s.Resource { 425 + case "blob": 426 + if len(s.Accept) == 0 { 427 + return fmt.Errorf("blob permission requires 'accept'") 428 + } 429 + for _, acc := range s.Accept { 430 + // TODO: more complete MIME pattern parsing 431 + parts := strings.SplitN(acc, "/", 3) 432 + if len(parts) != 2 || parts[0] == "*" || parts[0] == "" || parts[1] == "" { 433 + return fmt.Errorf("invalid blob 'accept' pattern: %s", acc) 434 + } 435 + } 436 + case "repo": 437 + if len(s.Collection) == 0 { 438 + return fmt.Errorf("repo permission requires 'collection'") 439 + } 440 + for _, coll := range s.Collection { 441 + if coll == "*" { 442 + continue 443 + } 444 + _, err := syntax.ParseNSID(coll) 445 + if err != nil { 446 + return fmt.Errorf("repo permission: %w", err) 447 + } 448 + } 449 + for _, act := range s.Action { 450 + if act != "create" && act != "update" && act != "delete" { 451 + return fmt.Errorf("unsupported repo action: %s", act) 452 + } 453 + } 454 + case "rpc": 455 + if len(s.LXM) == 0 { 456 + return fmt.Errorf("rpc permission requires 'lxm'") 457 + } 458 + for _, lxm := range s.LXM { 459 + if lxm == "*" { 460 + if s.Audience == "*" { 461 + // TODO: is this necessary here? 462 + return fmt.Errorf("can't have both 'lxm' and 'aud' be '*'") 463 + } 464 + continue 465 + } 466 + _, err := syntax.ParseNSID(lxm) 467 + if err != nil { 468 + return fmt.Errorf("rpc permission: %w", err) 469 + } 470 + } 471 + if (s.InheritAud == true && s.Audience != "") || (s.InheritAud == false && s.Audience == "") { 472 + return fmt.Errorf("rpc permission must have eith 'aud' or 'inheritAud' defined") 473 + } 474 + if s.Audience != "" { 475 + // TODO: helper for service refs 476 + parts := strings.SplitN(s.Audience, "#", 3) 477 + if len(parts) != 2 || parts[1] == "" { 478 + return fmt.Errorf("rpc 'aud' must be a service ref") 479 + } 480 + _, err := syntax.ParseDID(parts[0]) 481 + if err != nil { 482 + return fmt.Errorf("rpc 'aud' must be a service ref: %w", err) 483 + } 484 + } 485 + default: 486 + return fmt.Errorf("unsupported permission resource: %s", s.Resource) 487 + } 488 + return nil 489 + } 490 + 374 491 type SchemaBody struct { 375 492 Description *string `json:"description,omitempty"` 376 493 Encoding string `json:"encoding"` // required, mimetype ··· 803 920 } 804 921 805 922 func (s *SchemaParams) CheckSchema() error { 923 + if s.Type != "params" { 924 + return fmt.Errorf("expected 'params'") 925 + } 806 926 // TODO: check for set uniqueness of required 807 927 for _, k := range s.Required { 808 928 if _, ok := s.Properties[k]; !ok {
+58
atproto/lexicon/testdata/catalog/permission-set.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "example.lexicon.permissionset", 4 + "description": "exercizes many lexicon features for the permission-set type", 5 + "defs": { 6 + "main": { 7 + "type": "permission-set", 8 + "permissions": [ 9 + { 10 + "type": "permission", 11 + "resource": "blob", 12 + "accept": [ 13 + "image/*" 14 + ] 15 + }, 16 + { 17 + "type": "permission", 18 + "resource": "repo", 19 + "collection": [ 20 + "com.example.calendar.event", 21 + "com.example.calendar.rsvp" 22 + ], 23 + "action": [ 24 + "delete", 25 + "create" 26 + ] 27 + }, 28 + { 29 + "type": "permission", 30 + "resource": "repo", 31 + "collection": [ 32 + "com.example.calendar.event", 33 + "app.bsky.feed.post" 34 + ], 35 + "action": [ 36 + "create", 37 + "update", 38 + "delete" 39 + ] 40 + }, 41 + { 42 + "type": "permission", 43 + "resource": "rpc", 44 + "aud": "did:web:example.com#foo", 45 + "lxm": ["com.example.calendar.listEvents"] 46 + }, 47 + { 48 + "type": "permission", 49 + "resource": "rpc", 50 + "inheritAud": true, 51 + "lxm": [ 52 + "com.example.calendar.listEvents" 53 + ] 54 + } 55 + ] 56 + } 57 + } 58 + }