1
0
mirror of https://github.com/rancher/norman.git synced 2025-05-01 21:03:32 +00:00
norman/api/validate.go

90 lines
2.4 KiB
Go
Raw Permalink Normal View History

2017-11-11 04:44:02 +00:00
package api
2017-10-16 02:23:15 +00:00
import (
"crypto/rand"
"encoding/hex"
2017-11-11 04:44:02 +00:00
"fmt"
2017-10-16 02:23:15 +00:00
"net/http"
"github.com/rancher/norman/httperror"
2017-11-11 04:44:02 +00:00
"github.com/rancher/norman/parse"
"github.com/rancher/norman/types"
2017-10-16 02:23:15 +00:00
)
const (
csrfCookie = "CSRF"
csrfHeader = "X-API-CSRF"
)
2017-11-11 04:44:02 +00:00
func ValidateAction(request *types.APIContext) (*types.Action, error) {
if request.Action == "" || request.Link != "" || request.Method != http.MethodPost {
2017-11-11 04:44:02 +00:00
return nil, nil
}
actions := request.Schema.CollectionActions
if request.ID != "" {
actions = request.Schema.ResourceActions
}
action, ok := actions[request.Action]
if !ok {
2017-11-21 20:46:30 +00:00
return nil, httperror.NewAPIError(httperror.InvalidAction, fmt.Sprintf("Invalid action: %s", request.Action))
2017-11-11 04:44:02 +00:00
}
2017-12-16 02:22:10 +00:00
if request.ID != "" && request.ReferenceValidator != nil {
2017-11-11 04:44:02 +00:00
resource := request.ReferenceValidator.Lookup(request.Type, request.ID)
if resource == nil {
2017-11-21 20:46:30 +00:00
return nil, httperror.NewAPIError(httperror.NotFound, fmt.Sprintf("Failed to find type: %s id: %s", request.Type, request.ID))
2017-11-11 04:44:02 +00:00
}
if _, ok := resource.Actions[request.Action]; !ok {
2017-11-21 20:46:30 +00:00
return nil, httperror.NewAPIError(httperror.InvalidAction, fmt.Sprintf("Invalid action: %s", request.Action))
2017-11-11 04:44:02 +00:00
}
}
return &action, nil
}
func CheckCSRF(apiContext *types.APIContext) error {
if !parse.IsBrowser(apiContext.Request, false) {
2017-10-16 02:23:15 +00:00
return nil
}
cookie, err := apiContext.Request.Cookie(csrfCookie)
2017-11-11 04:44:02 +00:00
if err == http.ErrNoCookie {
2022-06-09 15:34:57 +00:00
// 16 bytes = 32 Hex Char = 128 bit entropy
bytes := make([]byte, 16)
2017-10-16 02:23:15 +00:00
_, err := rand.Read(bytes)
if err != nil {
2017-11-21 20:46:30 +00:00
return httperror.WrapAPIError(err, httperror.ServerError, "Failed in CSRF processing")
2017-10-16 02:23:15 +00:00
}
cookie = &http.Cookie{
2019-04-29 22:17:02 +00:00
Name: csrfCookie,
Value: hex.EncodeToString(bytes),
Path: "/",
2019-04-29 22:17:02 +00:00
Secure: true,
2017-10-16 02:23:15 +00:00
}
http.SetCookie(apiContext.Response, cookie)
2017-11-11 04:44:02 +00:00
} else if err != nil {
2017-11-21 20:46:30 +00:00
return httperror.NewAPIError(httperror.InvalidCSRFToken, "Failed to parse cookies")
}
// Not an else-if, because this should happen even if there was no cookie to begin with.
if apiContext.Method != http.MethodGet {
2017-10-16 02:23:15 +00:00
/*
* Very important to use apiContext.Method and not apiContext.Request.Method. The client can override the HTTP method with _method
2017-10-16 02:23:15 +00:00
*/
if cookie.Value == apiContext.Request.Header.Get(csrfHeader) {
2017-10-16 02:23:15 +00:00
// Good
} else if cookie.Value == apiContext.Request.URL.Query().Get(csrfCookie) {
2017-10-16 02:23:15 +00:00
// Good
} else {
2017-11-21 20:46:30 +00:00
return httperror.NewAPIError(httperror.InvalidCSRFToken, "Invalid CSRF token")
2017-10-16 02:23:15 +00:00
}
}
return nil
}