coupergateway / couper

Couper is a lightweight API gateway designed to support developers in building and operating API-driven Web projects
https://couper.io
MIT License
84 stars 15 forks source link

`seetie.MapToValue()` ignores keys starting with number #798

Closed johakoch closed 7 months ago

johakoch commented 7 months ago

If passing a map resulting from parsing

"Reader": {
    "251689107678426135": "some_value"
}

to seetie.MapToValue(), the result is cty.MapValEmpty(cty.NilType).

var validKey = regexp.MustCompile("[a-zA-Z_][a-zA-Z0-9_-]*")
...
func MapToValue(m map[string]interface{}) cty.Value {
...
    for k, v := range m {
        if !validKey.MatchString(k) {
            continue
        }
...
    if len(ctyMap) == 0 {
        return cty.MapValEmpty(cty.NilType) // prevent attribute access on nil values
    }

I don't see a reason for ignoring values of "number-only" keys.

So I removed

        if !validKey.MatchString(k) {
            continue
        }

You cannot use e.g.

request.context.oidc.userinfo.urn:foo:roles

(couper fails to start with

Missing attribute separator; Expected a newline or comma to mark the beginning of the next attribute.

) or

request.context.oidc.userinfo["urn:foo:roles"].Reader.251689107678426135

which is interpreted in HCL as a (numeric) LegacyIndex operator (couper panics with

not a list, map, or tuple type
...
goroutine 48 [running]:
runtime/debug.Stack()
    /usr/local/go/src/runtime/debug/stack.go:24 +0x65
github.com/coupergateway/couper/handler.(*Endpoint).ServeHTTP.func1()
    /home/.../src/couper/handler/endpoint.go:84 +0x7d
panic({0xd20ca0, 0xfe7700})
    /usr/local/go/src/runtime/panic.go:890 +0x263
github.com/zclconf/go-cty/cty.Value.HasIndex({{{0xff4648?, 0xc0002aab60?}}, {0xd69d40?, 0xc000388990?}}, {{{0xff4568?, 0xc00003a850?}}, {0xe86860?, 0xc00037a060?}})
    /home/.../src/couper/vendor/github.com/zclconf/go-cty/cty/value_ops.go:956 +0x8ae
github.com/coupergateway/couper/eval.walk({{{0xff4648?, 0xc0002aab60?}}, {0xd69d40?, 0xc000388990?}}, {{{0x0?, 0x0?}}, {0x0?, 0x0?}}, {0xc000140d60, 0x1, ...})
    /home/.../src/couper/eval/value.go:322 +0x32e
...

which is

        switch t.Key.Type() {
        case cty.Number:
            if variables.HasIndex(t.Key).True() {

)

But you should be able to at least use

request.context.oidc.userinfo["urn:foo:roles"].Reader["251689107678426135"]

BTW, validKey is also used in

func HeaderToMapValue(headers http.Header) cty.Value {
    ctyMap := make(map[string]cty.Value)
    for k, v := range headers {
        if validKey.MatchString(k) {
           ^^^^^^^^
            if len(v) == 0 {
                ctyMap[strings.ToLower(k)] = cty.StringVal("")
                continue
            }
            ctyMap[strings.ToLower(k)] = cty.StringVal(v[0]) // TODO: ListVal??
        }
    }
    if len(ctyMap) == 0 {
        return cty.MapValEmpty(cty.String)
    }
    return cty.MapVal(ctyMap)
}

I don't think it's common that HTTP header field names start with a number. But at least they may, according to RFC 9110:

   field-name     = token
   tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." /
    "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
   token = 1*tchar