grafana/pkg/web/binding.go
Dimitris Sotirakis 605d056136
Security: Sync security changes on main (#45083)
* * Teams: Appropriately apply user id filter in /api/teams/:id and /api/teams/search
* Teams: Ensure that users searching for teams are only able see teams they have access to
* Teams: Require teamGuardian admin privileges to list team members
* Teams: Prevent org viewers from administering teams
* Teams: Add org_id condition to team count query
* Teams: clarify permission requirements in teams api docs
* Teams: expand scenarios for team search tests
* Teams: mock teamGuardian in tests

Co-authored-by: Dan Cech <dcech@grafana.com>

* remove duplicate WHERE statement

* Fix for CVE-2022-21702

(cherry picked from commit 202d7c190082c094bc1dc13f7fe9464746c37f9e)

* Lint and test fixes

(cherry picked from commit 3e6b67d5504abf4a1d7b8d621f04d062c048e981)

* check content type properly

(cherry picked from commit 70b4458892bf2f776302720c10d24c9ff34edd98)

* basic csrf origin check

(cherry picked from commit 3adaa5ff39832364f6390881fb5b42ad47df92e1)

* compare origin to host

(cherry picked from commit 5443892699e8ed42836bb2b9a44744ff3e970f42)

* simplify url parsing

(cherry picked from commit b2ffbc9513fed75468628370a48b929d30af2b1d)

* check csrf for GET requests, only compare origin

(cherry picked from commit 8b81dc12d8f8a1f07852809c5b4d44f0f0b1d709)

* parse content type properly

(cherry picked from commit 16f76f4902e6f2188bea9606c68b551af186bdc0)

* mentioned get in the comment

(cherry picked from commit a7e61811ef8ae558ce721e2e3fed04ce7a5a5345)

* add content-type: application/json to test HTTP requests

* fix pluginproxy test

* Fix linter when comparing errors

Co-authored-by: Kevin Minehart <kmineh0151@gmail.com>
Co-authored-by: Dan Cech <dcech@grafana.com>
Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
Co-authored-by: Serge Zaitsev <serge.zaitsev@grafana.com>
Co-authored-by: Vardan Torosyan <vardants@gmail.com>
2022-02-09 13:44:38 +01:00

98 lines
2.4 KiB
Go

package web
import (
"encoding/json"
"errors"
"fmt"
"io"
"mime"
"net/http"
"reflect"
)
// Bind deserializes JSON payload from the request
func Bind(req *http.Request, v interface{}) error {
if req.Body != nil {
m, _, err := mime.ParseMediaType(req.Header.Get("Content-type"))
if err != nil {
return err
}
if m != "application/json" {
return errors.New("bad content type")
}
defer func() { _ = req.Body.Close() }()
err = json.NewDecoder(req.Body).Decode(v)
if err != nil && !errors.Is(err, io.EOF) {
return err
}
}
return validate(v)
}
type Validator interface {
Validate() error
}
func validate(obj interface{}) error {
// First check if obj is nil, because we cannot validate those.
if obj == nil {
return nil
}
// Second, check if obj has a nil interface value.
// This is to prevent panics when obj is an instance of uninitialised struct pointer / interface.
t := reflect.TypeOf(obj)
v := reflect.ValueOf(obj)
if v.Kind() == reflect.Ptr && v.IsNil() {
return nil
}
// If type has a Validate() method - use that
if validator, ok := obj.(Validator); ok {
return validator.Validate()
}
// Otherwise, use reflection to match `binding:"Required"` struct field tags.
// Resolve all pointers and interfaces, until we get a concrete type.
for v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr {
t = t.Elem()
v = v.Elem()
}
switch v.Kind() {
// For arrays and slices - iterate over each element and validate it recursively
case reflect.Slice, reflect.Array:
for i := 0; i < v.Len(); i++ {
e := v.Index(i).Interface()
if err := validate(e); err != nil {
return err
}
}
// For structs - iterate over each field, check for the "Required" constraint (Macaron legacy), then validate it recursively
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
field := t.Field(i)
value := v.Field(i)
rule := field.Tag.Get("binding")
if !value.CanInterface() {
continue
}
if rule == "Required" {
zero := reflect.Zero(field.Type).Interface()
if value.Kind() == reflect.Slice {
if value.Len() == 0 {
return fmt.Errorf("required slice %s must not be empty", field.Name)
}
} else if reflect.DeepEqual(zero, value.Interface()) {
return fmt.Errorf("required value %s must not be empty", field.Name)
}
}
if err := validate(value.Interface()); err != nil {
return err
}
}
default: // ignore
}
return nil
}