this repo has no description
13
fork

Configure Feed

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

iterate on parsing

+509 -151
+217 -101
atproto/auth/permission.go
··· 4 4 "errors" 5 5 "fmt" 6 6 "net/url" 7 - "strconv" 8 7 "strings" 8 + 9 + "github.com/bluesky-social/indigo/atproto/syntax" 9 10 ) 10 11 11 12 var ( ··· 13 14 ErrUnknownScope = errors.New("unknown scope type") 14 15 ) 15 16 17 + type GenericPermission struct { 18 + Resource string `json:"resource"` 19 + Positional string `json:"positional"` 20 + Params url.Values `json:"params"` 21 + } 22 + 16 23 type Permission struct { 17 24 Type string `json:"type,omitempty"` 18 25 Resource string `json:"resource"` 19 26 27 + // common params (eg, identity, account) 28 + Attribute string `json:"attr,omitempty"` 29 + Action []string `json:"action,omitempty"` 30 + Audience string `json:"aud,omitempty"` 31 + 20 32 // repo 21 33 Collections []string `json:"collection,omitempty"` 22 - Action string `json:"action,omitempty"` 23 34 24 35 // rpc 25 36 Endpoints []string `json:"lxm,omitempty"` 26 - Audience string `json:"aud,omitempty"` 27 37 28 38 // blob 29 - MaxSize *uint64 `json:"maxSize,omitempty"` 30 - Accept []string `json:"accept,omitempty"` 31 - 32 - // account 33 - Read []string `json:"read,omitempty"` 34 - Manage []string `json:"manage,omitempty"` 35 - 36 - // identity 37 - DID []string `json:"did,omitempty"` 38 - PLC []string `json:"plc,omitempty"` 39 + Accept []string `json:"accept,omitempty"` 39 40 40 41 // include 41 - PermissionSet string `json:"permissionSet,omitempty"` 42 + NSID string `json:"nsid,omitempty"` 42 43 } 43 44 44 45 func (p *Permission) Scope() string { ··· 47 48 params := make(url.Values) 48 49 49 50 switch p.Resource { 50 - case "repo": 51 - if len(p.Collections) == 1 { 52 - positional = p.Collections[0] 53 - } else if len(p.Collections) > 1 { 54 - params["collection"] = p.Collections 55 - } 56 - if p.Action != "" { 57 - params.Set("action", p.Action) 58 - } 59 - case "rpc": 60 - if len(p.Endpoints) == 1 { 61 - positional = p.Endpoints[0] 62 - } else if len(p.Endpoints) > 1 { 63 - params["lxm"] = p.Endpoints 51 + case "account": 52 + if p.Attribute != "" { 53 + positional = p.Attribute 64 54 } 65 - if p.Audience != "" { 66 - params.Set("aud", p.Audience) 55 + if len(p.Action) != 0 { 56 + params["action"] = p.Action 67 57 } 68 58 case "blob": 69 - if p.MaxSize != nil { 70 - params.Set("maxSize", strconv.Itoa(int(*p.MaxSize))) 71 - } 72 59 if len(p.Accept) == 1 { 73 60 positional = p.Accept[0] 74 61 } else if len(p.Accept) > 1 { 75 62 params["accept"] = p.Accept 76 63 } 77 - case "account": 78 - if len(p.Read) == 1 { 79 - positional = p.Read[0] 80 - } else if len(p.Read) > 1 { 81 - params["read"] = p.Read 82 - } 83 - if len(p.Manage) > 0 { 84 - params["manage"] = p.Manage 85 - } 86 64 case "identity": 87 - if len(p.DID) == 1 { 88 - positional = p.DID[0] 89 - } else if len(p.DID) > 1 { 90 - params["did"] = p.DID 65 + if p.Attribute != "" { 66 + positional = p.Attribute 91 67 } 92 - if len(p.PLC) > 0 { 93 - params["plc"] = p.PLC 68 + if len(p.Action) != 0 { 69 + params["action"] = p.Action 94 70 } 95 71 case "include": 96 - if p.PermissionSet != "" { 97 - positional = p.PermissionSet 72 + if p.NSID != "" { 73 + positional = p.NSID 98 74 } 99 75 // TODO: other params... 100 76 if p.Audience != "" { 101 77 params.Set("aud", p.Audience) 102 78 } 79 + case "repo": 80 + if len(p.Collections) == 1 { 81 + positional = p.Collections[0] 82 + } else if len(p.Collections) > 1 { 83 + params["collection"] = p.Collections 84 + } 85 + if len(p.Action) != 0 { 86 + params["action"] = p.Action 87 + } 88 + case "rpc": 89 + if len(p.Endpoints) == 1 { 90 + positional = p.Endpoints[0] 91 + } else if len(p.Endpoints) > 1 { 92 + params["lxm"] = p.Endpoints 93 + } 94 + if p.Audience != "" { 95 + params.Set("aud", p.Audience) 96 + } 103 97 default: 104 98 return "" 105 99 } ··· 114 108 return scope 115 109 } 116 110 117 - func ParseScope(scope string) (*Permission, error) { 111 + func ParseGenericScope(scope string) (*GenericPermission, error) { 118 112 119 113 front, query, _ := strings.Cut(scope, "?") 120 114 resource, positional, _ := strings.Cut(front, ":") ··· 124 118 return nil, fmt.Errorf("%w: %w", ErrInvalidPermissionSyntax, err) 125 119 } 126 120 121 + p := GenericPermission{ 122 + Resource: resource, 123 + Positional: positional, 124 + Params: params, 125 + } 126 + return &p, nil 127 + } 128 + 129 + // TODO: improve this function 130 + func validBlobAccept(accept string) bool { 131 + if accept == "*/*" { 132 + return true 133 + } 134 + parts := strings.SplitN(accept, "/", 3) 135 + if len(parts) != 2 { 136 + return false 137 + } 138 + if parts[0] == "*" { 139 + return false 140 + } 141 + if parts[1] == "**" { 142 + return false 143 + } 144 + return true 145 + } 146 + 147 + // TODO: improve this function 148 + func validServiceRef(accept string) bool { 149 + parts := strings.SplitN(accept, "#", 3) 150 + if len(parts) != 2 { 151 + return false 152 + } 153 + _, err := syntax.ParseDID(parts[0]) 154 + if err != nil { 155 + return false 156 + } 157 + if len(parts[1]) == 0 { 158 + return false 159 + } 160 + return true 161 + } 162 + 163 + func ParseScope(scope string) (*Permission, error) { 164 + // TODO: should unknown params be an error? 165 + 166 + g, err := ParseGenericScope(scope) 167 + if err != nil { 168 + return nil, err 169 + } 170 + 127 171 p := Permission{ 128 172 Type: "permission", 129 - Resource: resource, 173 + Resource: g.Resource, 130 174 } 131 175 132 - // TODO: should unknown fields be an error? 133 - // TODO: could pre-parse in all the various fields? and then just positional per type 134 - switch resource { 135 - case "repo": 136 - if params.Has("collection") { 137 - if positional != "" { 176 + switch g.Resource { 177 + case "account": 178 + if g.Params.Has("attr") { 179 + if g.Positional != "" || len(g.Params["attr"]) != 1 { 138 180 return nil, ErrInvalidPermissionSyntax 139 181 } 140 - p.Collections = params["collection"] 182 + p.Attribute = g.Params.Get("attr") 141 183 } 142 - if positional != "" { 143 - p.Collections = []string{positional} 184 + if g.Positional != "" { 185 + p.Attribute = g.Positional 144 186 } 145 - p.Action = params.Get("action") 146 - case "rpc": 147 - if params.Has("lxm") { 148 - if positional != "" { 187 + if p.Attribute == "" { 188 + return nil, ErrInvalidPermissionSyntax 189 + } 190 + if p.Attribute != "" && p.Attribute != "email" && p.Attribute != "repo" && p.Attribute != "status" { 191 + return nil, ErrInvalidPermissionSyntax 192 + } 193 + if len(g.Params["action"]) > 1 { 194 + return nil, ErrInvalidPermissionSyntax 195 + } 196 + p.Action = g.Params["action"] 197 + for _, act := range p.Action { 198 + if act != "read" && act != "manage" { 149 199 return nil, ErrInvalidPermissionSyntax 150 200 } 151 - p.Endpoints = params["lxm"] 152 - } 153 - if positional != "" { 154 - p.Endpoints = []string{positional} 155 201 } 156 - p.Audience = params.Get("aud") 157 202 case "blob": 158 - if params.Has("accept") { 159 - if positional != "" { 203 + if g.Params.Has("accept") { 204 + if g.Positional != "" { 160 205 return nil, ErrInvalidPermissionSyntax 161 206 } 162 - p.Accept = params["accept"] 207 + p.Accept = g.Params["accept"] 163 208 } 164 - if positional != "" { 165 - p.Accept = []string{positional} 209 + if g.Positional != "" { 210 + p.Accept = []string{g.Positional} 166 211 } 167 - if params.Has("maxSize") { 168 - v, err := strconv.ParseUint(params.Get("maxSize"), 10, 64) 169 - if err != nil { 170 - return nil, fmt.Errorf("%w: %w", ErrInvalidPermissionSyntax, err) 171 - } 172 - p.MaxSize = &v 212 + if len(p.Accept) == 0 { 213 + return nil, ErrInvalidPermissionSyntax 173 214 } 174 - case "account": 175 - if params.Has("read") { 176 - if positional != "" { 215 + for _, acc := range p.Accept { 216 + if !validBlobAccept(acc) { 177 217 return nil, ErrInvalidPermissionSyntax 178 218 } 179 - p.Read = params["read"] 180 219 } 181 - if positional != "" { 182 - p.Read = []string{positional} 183 - } 184 - p.Manage = params["manage"] 185 220 case "identity": 186 - if params.Has("did") { 187 - if positional != "" { 221 + if g.Params.Has("attr") { 222 + if g.Positional != "" || len(g.Params["attr"]) != 1 { 188 223 return nil, ErrInvalidPermissionSyntax 189 224 } 190 - p.DID = params["did"] 225 + p.Attribute = g.Params.Get("attr") 191 226 } 192 - if positional != "" { 193 - p.DID = []string{positional} 227 + if g.Positional != "" { 228 + p.Attribute = g.Positional 194 229 } 195 - p.PLC = params["plc"] 230 + if p.Attribute != "*" && p.Attribute != "handle" { 231 + return nil, ErrInvalidPermissionSyntax 232 + } 233 + if len(g.Params["action"]) > 1 { 234 + return nil, ErrInvalidPermissionSyntax 235 + } 236 + p.Action = g.Params["action"] 237 + for _, act := range p.Action { 238 + if act != "manage" && act != "submit" { 239 + return nil, ErrInvalidPermissionSyntax 240 + } 241 + } 196 242 case "include": 197 - if params.Has("permissionSet") { 198 - if positional != "" { 243 + if g.Params.Has("nsid") { 244 + if g.Positional != "" || len(g.Params["nsid"]) != 1 { 199 245 return nil, ErrInvalidPermissionSyntax 200 246 } 201 - p.PermissionSet = params.Get("permissionSet") 247 + p.NSID = g.Params.Get("nsid") 202 248 } 203 - if positional != "" { 204 - p.PermissionSet = positional 249 + if g.Positional != "" { 250 + p.NSID = g.Positional 251 + } 252 + _, err := syntax.ParseNSID(p.NSID) 253 + if err != nil { 254 + return nil, fmt.Errorf("%w: %w", ErrInvalidPermissionSyntax, err) 255 + } 256 + if g.Params.Has("aud") && (len(g.Params["aud"]) != 1 || g.Params.Get("aud") == "") { 257 + return nil, ErrInvalidPermissionSyntax 258 + } 259 + p.Audience = g.Params.Get("aud") 260 + if p.Audience != "" && p.Audience != "*" && !validServiceRef(p.Audience) { 261 + return nil, ErrInvalidPermissionSyntax 205 262 } 206 263 // TODO: also parse most other params... 207 - p.Audience = params.Get("aud") 264 + case "repo": 265 + if g.Params.Has("collection") { 266 + if g.Positional != "" { 267 + return nil, ErrInvalidPermissionSyntax 268 + } 269 + p.Collections = g.Params["collection"] 270 + } 271 + if g.Positional != "" { 272 + p.Collections = []string{g.Positional} 273 + } 274 + if len(p.Collections) == 0 { 275 + return nil, ErrInvalidPermissionSyntax 276 + } 277 + for _, coll := range p.Collections { 278 + if coll == "*" { 279 + continue 280 + } 281 + _, err := syntax.ParseNSID(coll) 282 + if err != nil { 283 + return nil, fmt.Errorf("%w: %w", ErrInvalidPermissionSyntax, err) 284 + } 285 + } 286 + p.Action = g.Params["action"] 287 + for _, act := range p.Action { 288 + if act != "create" && act != "update" && act != "delete" { 289 + return nil, ErrInvalidPermissionSyntax 290 + } 291 + } 292 + case "rpc": 293 + if g.Params.Has("lxm") { 294 + if g.Positional != "" { 295 + return nil, ErrInvalidPermissionSyntax 296 + } 297 + p.Endpoints = g.Params["lxm"] 298 + } 299 + if g.Positional != "" { 300 + p.Endpoints = []string{g.Positional} 301 + } 302 + if len(p.Endpoints) == 0 { 303 + return nil, ErrInvalidPermissionSyntax 304 + } 305 + if len(g.Params["aud"]) != 1 { 306 + return nil, ErrInvalidPermissionSyntax 307 + } 308 + p.Audience = g.Params.Get("aud") 309 + for _, nsid := range p.Endpoints { 310 + if nsid == "*" { 311 + if p.Audience == "*" { 312 + return nil, ErrInvalidPermissionSyntax 313 + } 314 + continue 315 + } 316 + _, err := syntax.ParseNSID(nsid) 317 + if err != nil { 318 + return nil, fmt.Errorf("%w: %w", ErrInvalidPermissionSyntax, err) 319 + } 320 + } 321 + if p.Audience != "*" && !validServiceRef(p.Audience) { 322 + return nil, ErrInvalidPermissionSyntax 323 + } 208 324 default: 209 325 return nil, ErrUnknownScope 210 326 }
+50 -26
atproto/auth/permission_test.go
··· 2 2 3 3 import ( 4 4 "bufio" 5 + "encoding/json" 5 6 "fmt" 6 7 "os" 7 8 "testing" ··· 14 15 15 16 // NOTE: this escapes colons and slashes, which aren't strictly necessary 16 17 testScopes := []string{ 17 - "repo:com.example.record?action=all", 18 - "repo?action=all&collection=com.example.record&collection=com.example.other", 18 + "repo:com.example.record?action=delete", 19 + "repo?action=delete&collection=com.example.record&collection=com.example.other", 19 20 "rpc:com.example.query?aud=did%3Aweb%3Aapi.example.com%23frag", 20 21 "rpc?aud=did%3Aweb%3Aapi.example.com%23frag&lxm=com.example.query&lxm=com.example.procedure", 21 22 "blob:image/*", 22 - "blob?accept=image%2Fpng&accept=image%2Fjpeg&maxSize=123", 23 - "account:email?manage=deactivate", 24 - "identity:handle?plc=rotation", 23 + "blob?accept=image%2Fpng&accept=image%2Fjpeg", 24 + "account:email?action=manage", 25 + "identity:handle?action=submit", 25 26 "include:app.example.authBasics", 26 27 } 27 28 ··· 29 30 p, err := ParseScope(scope) 30 31 assert.NoError(err) 31 32 if err != nil { 33 + fmt.Println("BAD: " + scope) 32 34 continue 33 35 } 34 36 assert.Equal(scope, p.Scope()) 35 37 } 36 38 } 37 39 40 + type GenericExample struct { 41 + Scope string `json:"scope"` 42 + Generic GenericPermission `json:"generic"` 43 + } 44 + 45 + func TestGenericPermissions(t *testing.T) { 46 + assert := assert.New(t) 47 + file, err := os.Open("testdata/generic_scopes.json") 48 + if err != nil { 49 + assert.NoError(err) 50 + t.Fail() 51 + } 52 + defer file.Close() 53 + 54 + var fixtures []GenericExample 55 + if err := json.NewDecoder(file).Decode(&fixtures); err != nil { 56 + assert.NoError(err) 57 + t.Fail() 58 + } 59 + 60 + for _, fix := range fixtures { 61 + gp, err := ParseGenericScope(fix.Scope) 62 + if err != nil { 63 + fmt.Println("BAD: " + fix.Scope) 64 + assert.NoError(err) 65 + continue 66 + } 67 + assert.Equal(fix.Generic, *gp) 68 + } 69 + } 70 + 38 71 func TestInteropPermissionValid(t *testing.T) { 39 72 assert := assert.New(t) 40 73 file, err := os.Open("testdata/permission_scopes_valid.txt") 41 - assert.NoError(err) 74 + if err != nil { 75 + assert.NoError(err) 76 + t.Fail() 77 + } 42 78 defer file.Close() 43 79 scanner := bufio.NewScanner(file) 44 80 for scanner.Scan() { ··· 46 82 if len(line) == 0 || line[0] == '#' { 47 83 continue 48 84 } 85 + _, err := ParseGenericScope(line) 86 + if err != nil { 87 + fmt.Println("BAD: " + line) 88 + } 89 + assert.NoError(err) 49 90 p, err := ParseScope(line) 50 91 if err != nil { 51 92 fmt.Println("BAD: " + line) ··· 61 102 func TestInteropPermissionInvalid(t *testing.T) { 62 103 assert := assert.New(t) 63 104 file, err := os.Open("testdata/permission_scopes_invalid.txt") 64 - assert.NoError(err) 65 - defer file.Close() 66 - scanner := bufio.NewScanner(file) 67 - for scanner.Scan() { 68 - line := scanner.Text() 69 - if len(line) == 0 || line[0] == '#' { 70 - continue 71 - } 72 - _, err := ParseScope(line) 73 - if err == nil { 74 - fmt.Println("BAD: " + line) 75 - } 76 - assert.Error(err) 105 + if err != nil { 106 + assert.NoError(err) 107 + t.Fail() 77 108 } 78 - assert.NoError(scanner.Err()) 79 - } 80 - 81 - func TestInteropPermissionOther(t *testing.T) { 82 - assert := assert.New(t) 83 - file, err := os.Open("testdata/permission_scopes_other.txt") 84 - assert.NoError(err) 85 109 defer file.Close() 86 110 scanner := bufio.NewScanner(file) 87 111 for scanner.Scan() {
+97
atproto/auth/testdata/generic_scopes.json
··· 1 + [ 2 + { 3 + "scope": "res:pos?p=true", 4 + "generic": { 5 + "resource": "res", 6 + "positional": "pos", 7 + "params": { 8 + "p": ["true"] 9 + } 10 + } 11 + }, 12 + { 13 + "scope": "my-res", 14 + "generic": { 15 + "resource": "my-res", 16 + "positional": "", 17 + "params": {} 18 + } 19 + }, 20 + { 21 + "scope": "my-res:my-pos", 22 + "generic": { 23 + "resource": "my-res", 24 + "positional": "my-pos", 25 + "params": {} 26 + } 27 + }, 28 + { 29 + "scope": "my-res:", 30 + "generic": { 31 + "resource": "my-res", 32 + "positional": "", 33 + "params": {} 34 + } 35 + }, 36 + { 37 + "scope": "my-res:foo?x=value&y=value-y", 38 + "generic": { 39 + "resource": "my-res", 40 + "positional": "foo", 41 + "params": { 42 + "x": ["value"], 43 + "y": ["value-y"] 44 + } 45 + } 46 + }, 47 + { 48 + "scope": "my-res?x=value&y=value-y", 49 + "generic": { 50 + "resource": "my-res", 51 + "positional": "", 52 + "params": { 53 + "x": ["value"], 54 + "y": ["value-y"] 55 + } 56 + } 57 + }, 58 + { 59 + "scope": "my-res?x=foo&x=bar&x=baz", 60 + "generic": { 61 + "resource": "my-res", 62 + "positional": "", 63 + "params": { 64 + "x": ["foo", "bar", "baz"] 65 + } 66 + } 67 + }, 68 + 69 + { 70 + "scope": "rpc:foo.bar?aud=did:foo:bar?lxm=bar.baz", 71 + "generic": { 72 + "resource": "rpc", 73 + "positional": "foo.bar", 74 + "params": { 75 + "aud": ["did:foo:bar?lxm=bar.baz"] 76 + } 77 + } 78 + }, 79 + { 80 + "scope": "my-res?x=my%20value", 81 + "generic": { 82 + "resource": "my-res", 83 + "positional": "", 84 + "params": { 85 + "x": ["my value"] 86 + } 87 + } 88 + }, 89 + { 90 + "scope": "my-res:my:pos", 91 + "generic": { 92 + "resource": "my-res", 93 + "positional": "my:pos", 94 + "params": {} 95 + } 96 + } 97 + ]
+10
atproto/auth/testdata/generic_scopes_SKIP.json
··· 1 + [ 2 + { 3 + "scope": "my-res:my%20pos", 4 + "generic": { 5 + "resource": "my-res", 6 + "positional": "my pos", 7 + "params": {} 8 + } 9 + } 10 + ]
+97 -11
atproto/auth/testdata/permission_scopes_invalid.txt
··· 1 1 2 - blob:image/png?maxSize=-123 3 - blob:image/png?maxSize=blah 4 - blob:image/png?maxSize 5 - blob:image/png?maxSize=123?maxSize=123 2 + invalid 3 + scope 4 + invalid:email 5 + 6 + account:invalid 7 + account:email?action=invalid 8 + account 9 + account: 10 + 11 + blob 12 + blob:invalid 13 + blob?accept=invalid-mime 14 + blob?accept=invalid 15 + blob:*/** 16 + blob:*/png 6 17 7 - # TODO: these partial strings 8 - #repo:123 9 - #repo 10 - #repo: 11 - #rpc:123 12 - #rpc 13 - #rpc:com.example.query?aud=api.example.com 18 + identity:invalid 19 + identity:*?attr=* 20 + identity:*?action=* 21 + identity:invalid 22 + identity:handle?action=invalid 23 + identity?attribute=invalid&action=invalid 24 + 25 + # TODO: will be supported? 26 + #identity:*?action=manage 27 + #identity:*?action=submit 28 + 29 + include 30 + include# 31 + 32 + # invalid NSID 33 + include: 34 + include:# 35 + include:& 36 + include:com..example 37 + include:com 38 + include:com.example 39 + include:9com.example.foo 40 + include:com.example.-bar 41 + include:invalid^nsid 42 + include:nsid 43 + 44 + # invalid AUD 45 + include:com.example.baz?aud= 46 + include:com.example.baz?aud=did:web:example.com 47 + include:com.example.baz?aud=invalid^did 48 + include:com.example.baz?aud=invalid^did 49 + 50 + repo:foo bar 51 + repo:.foo 52 + repo:bar. 53 + repo:com.example.foo?action=invalid 54 + repo:123 55 + repo 56 + repo: 57 + repo:*?action=* 58 + repo:invalid 59 + repo:com.example.foo?action=invalid 60 + repo?collection=invalid&action=invalid 61 + 62 + rpc 63 + rpc:123 64 + rpc:com.example.method1?aud=did:web:example.com&lxm=com.example.method2 65 + rpc:com.example.query?aud=api.example.com 66 + rpc?aud=*&lxm=* 67 + rpc:invalid 68 + rpc?lxm=invalid 69 + rpc:* 70 + rpc:invalid?aud=did:web:example.com 71 + rpc:invalid?aud=did:web:example.com%23service_id 72 + rpc:foo.bar 73 + rpc:foo.bar.baz?aud=did:web 74 + rpc:foo.bar.baz?aud=did:web%23service_id 75 + rpc:foo.bar.baz?aud=did:plc:111 76 + rpc:foo.bar.baz?aud=did:foo:bar 77 + rpc:foo.bar.baz?aud=did:web:example.com%23service_id&lxm=foo.bar.baz 78 + rpc:foo.bar.baz?aud=invalid 79 + rpc:invalid?aud=did:web:example.com 80 + rpc:invalid?aud=did:web:example.com%23service_id 81 + rpc:com.example.service?aud=invalid 82 + notrpc:com.example.service?aud=did:web:example.com%23service_id 83 + rpc?lxm=invalid&aud=invalid 84 + 85 + # TODO: DID-level parsing 86 + #rpc:foo.bar.baz?aud=did:plc:111%23service_id 87 + #rpc:foo.bar.baz?aud=did:foo:bar%23service_id 88 + 89 + # TODO: unknown param validation 90 + #rpc:com.example.service?aud=did:web:example.com%23service_id&invalid=param 91 + 92 + # missing LXM 93 + rpc?aud=did:web:example.com%23service_id 94 + rpc:?aud=did:web:example.com%23service_id 95 + rpc?aud=did:web:example.com 96 + 97 + # missing AUD 98 + rpc?lxm=com.example.method1 99 + rpc:com.example.method1
-3
atproto/auth/testdata/permission_scopes_other.txt
··· 1 - atproto 2 - blah 3 - unknown:resource?type=true
+38 -10
atproto/auth/testdata/permission_scopes_valid.txt
··· 1 - repo:com.example.record 2 - repo:com.example.record?action=* 3 - repo:* 4 - repo?action=all&collection=com.example.record&collection=com.example.other 5 1 6 - rpc:com.example.query?aud=did:web:api.example.com%23api_example 7 - rpc?aud=did%3Aweb%3Aapi.example.com%23frag&lxm=com.example.query&lxm=com.example.procedure 2 + account:email?action=read 3 + account:email?action=manage 4 + account:repo?action=manage 5 + account:status?action=manage 6 + account:status 7 + account:email 8 + account:repo 8 9 9 - blob:image/*?maxSize=2000 10 - blob?accept=image%2Fpng&accept=image%2Fjpeg&maxSize=123 10 + blob:image/png 11 + blob:*/* 12 + blob:image/* 11 13 12 - account:email?manage=deactivate 13 - identity:handle?plc=rotation 14 + identity:handle 15 + identity:* 16 + identity?attr=handle 17 + identity?attr=handle&action=manage 18 + identity:*?action=manage 19 + identity:*?action=submit 20 + 14 21 include:app.example.authBasics 22 + include:com.example.bar 23 + include:com.example.baz?aud=did:web:example.com%23my_service 24 + include:com.example.baz?aud=did:web:example.com#my_service 25 + include?nsid=com.example.baz 26 + include?aud=did:web:example.com%23my_service&nsid=com.example.baz 27 + include:com.example.calendar.auth 28 + 29 + repo:com.example.foo 30 + repo:com.example.foo?action=create&action=update 31 + repo:*?action=create 32 + repo:* 33 + repo?collection=com.example.foo&action=create&action=update&action=delete 34 + repo?action=create&collection=com.example.foo&collection=com.example.bar 35 + 36 + rpc:com.example.service?aud=did:web:example.com%23service_id 37 + rpc?lxm=com.example.method1&aud=* 38 + rpc:com.example.method1?aud=* 39 + rpc?lxm=com.example.method1&lxm=com.example.method2&aud=did:web:example.com%23service_id 40 + rpc?aud=*&lxm=com.example.method1&lxm=com.example.method2 41 + rpc:com.example.query?aud=did:web:api.example.com%23api_example 42 + rpc?aud=did%3Aweb%3Aapi.example.com%23frag&lxm=com.example.query&lxm=com.example.procedure