go scratch code for atproto

split out some permission parsing code

+104 -75
+9 -75
permissions/permission.go
··· 4 4 "errors" 5 5 "fmt" 6 6 "net/url" 7 - "strings" 8 - "unicode" 9 7 10 8 "github.com/bluesky-social/indigo/atproto/syntax" 11 9 ) ··· 16 14 ErrUnknownResource = errors.New("unknown permission resource") 17 15 ) 18 16 19 - type GenericPermission struct { 20 - Resource string `json:"resource"` 21 - Positional string `json:"positional"` 22 - Params url.Values `json:"params"` 23 - } 24 - 17 + // Parsed components of an AT permission, as currently specified. 18 + // 19 + // This type is somewhat redundant with the "SchemaPermission" type in the indigo lexicon package, but it can represent all possible permissions, not just those found in permission sets. 25 20 type Permission struct { 26 21 Type string `json:"type,omitempty"` 27 22 Resource string `json:"resource"` ··· 37 32 NSID string `json:"nsid,omitempty"` 38 33 } 39 34 35 + // Renders a permission as a permission scope string. 36 + // 37 + // If the permission contains information which only makes sense in the context of a permission-set (eg, the inheritAud flag), it will be silently dropped. 40 38 func (p *Permission) ScopeString() string { 41 39 42 40 positional := "" ··· 99 97 return scope 100 98 } 101 99 102 - func ParseGenericScope(scope string) (*GenericPermission, error) { 103 - 104 - if !isASCII(scope) { 105 - return nil, ErrInvalidPermissionSyntax 106 - } 107 - 108 - front, query, _ := strings.Cut(scope, "?") 109 - resource, positional, _ := strings.Cut(front, ":") 110 - 111 - // XXX: more charset restrictions 112 - 113 - params, err := url.ParseQuery(query) 114 - if err != nil { 115 - return nil, fmt.Errorf("%w: %w", ErrInvalidPermissionSyntax, err) 116 - } 117 - 118 - p := GenericPermission{ 119 - Resource: resource, 120 - Positional: positional, 121 - Params: params, 122 - } 123 - return &p, nil 124 - } 125 - 126 - // TODO: improve this function, add tests 127 - func validBlobAccept(accept string) bool { 128 - if accept == "*/*" { 129 - return true 130 - } 131 - parts := strings.SplitN(accept, "/", 3) 132 - if len(parts) != 2 { 133 - return false 134 - } 135 - if parts[0] == "*" { 136 - return false 137 - } 138 - if parts[1] == "**" { 139 - return false 140 - } 141 - return true 142 - } 143 - 144 - // TODO: improve this function, add tests 145 - func validServiceRef(accept string) bool { 146 - parts := strings.SplitN(accept, "#", 3) 147 - if len(parts) != 2 { 148 - return false 149 - } 150 - _, err := syntax.ParseDID(parts[0]) 151 - if err != nil { 152 - return false 153 - } 154 - if len(parts[1]) == 0 { 155 - return false 156 - } 157 - return true 158 - } 159 - 100 + // Parses a permission scope string (as would be found as a component of an OAuth scope string) into a [Permission]. 101 + // 102 + // This function is strict: it is case sensitive, verifies field syntax, and will throw an error on unknown parameters/fields. Note that calling code is usually supposed to simply skip any permission which cause such errors, not reject entire requests. 160 103 func ParsePermissionString(scope string) (*Permission, error) { 161 104 g, err := ParseGenericScope(scope) 162 105 if err != nil { ··· 343 286 } 344 287 return &p, nil 345 288 } 346 - 347 - func isASCII(s string) bool { 348 - for i := 0; i < len(s); i++ { 349 - if s[i] > unicode.MaxASCII { 350 - return false 351 - } 352 - } 353 - return true 354 - }
+95
permissions/util.go
··· 1 + package permissions 2 + 3 + import ( 4 + "fmt" 5 + "net/url" 6 + "strings" 7 + "unicode" 8 + 9 + "github.com/bluesky-social/indigo/atproto/syntax" 10 + ) 11 + 12 + // Parsed components of a generic AT scope string. This is for internal or low-level use; most code should use [ParsePermissionString] instead. 13 + type GenericPermission struct { 14 + Resource string `json:"resource"` 15 + Positional string `json:"positional"` 16 + Params url.Values `json:"params"` 17 + } 18 + 19 + func ParseGenericScope(scope string) (*GenericPermission, error) { 20 + 21 + if !isASCII(scope) { 22 + return nil, ErrInvalidPermissionSyntax 23 + } 24 + 25 + front, query, _ := strings.Cut(scope, "?") 26 + resource, positional, _ := strings.Cut(front, ":") 27 + 28 + // XXX: more charset restrictions 29 + 30 + params, err := url.ParseQuery(query) 31 + if err != nil { 32 + return nil, fmt.Errorf("%w: %w", ErrInvalidPermissionSyntax, err) 33 + } 34 + 35 + p := GenericPermission{ 36 + Resource: resource, 37 + Positional: positional, 38 + Params: params, 39 + } 40 + return &p, nil 41 + } 42 + 43 + func (p *GenericPermission) ScopeString() string { 44 + scope := p.Resource 45 + if p.Positional != "" { 46 + scope = scope + ":" + p.Positional 47 + } 48 + if len(p.Params) > 0 { 49 + scope = scope + "?" + p.Params.Encode() 50 + } 51 + return scope 52 + } 53 + 54 + // TODO: replace with helper in syntax pkg 55 + func validBlobAccept(accept string) bool { 56 + if accept == "*/*" { 57 + return true 58 + } 59 + parts := strings.SplitN(accept, "/", 3) 60 + if len(parts) != 2 { 61 + return false 62 + } 63 + if parts[0] == "*" { 64 + return false 65 + } 66 + if parts[1] == "**" { 67 + return false 68 + } 69 + return true 70 + } 71 + 72 + // TODO: replace with helper in syntax pkg 73 + func validServiceRef(accept string) bool { 74 + parts := strings.SplitN(accept, "#", 3) 75 + if len(parts) != 2 { 76 + return false 77 + } 78 + _, err := syntax.ParseDID(parts[0]) 79 + if err != nil { 80 + return false 81 + } 82 + if len(parts[1]) == 0 { 83 + return false 84 + } 85 + return true 86 + } 87 + 88 + func isASCII(s string) bool { 89 + for i := 0; i < len(s); i++ { 90 + if s[i] > unicode.MaxASCII { 91 + return false 92 + } 93 + } 94 + return true 95 + }