04d81e1c27
* report intermediate error messages during request forwarding * CL
219 lines
6.5 KiB
Go
219 lines
6.5 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package logical
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/hashicorp/errwrap"
|
|
multierror "github.com/hashicorp/go-multierror"
|
|
"github.com/hashicorp/vault/sdk/helper/consts"
|
|
)
|
|
|
|
// RespondErrorCommon pulls most of the functionality from http's
|
|
// respondErrorCommon and some of http's handleLogical and makes it available
|
|
// to both the http package and elsewhere.
|
|
func RespondErrorCommon(req *Request, resp *Response, err error) (int, error) {
|
|
if err == nil && (resp == nil || !resp.IsError()) {
|
|
switch {
|
|
case req.Operation == ReadOperation || req.Operation == HeaderOperation:
|
|
if resp == nil {
|
|
return http.StatusNotFound, nil
|
|
}
|
|
|
|
// Basically: if we have empty "keys" or no keys at all, 404. This
|
|
// provides consistency with GET.
|
|
case req.Operation == ListOperation && (resp == nil || resp.WrapInfo == nil):
|
|
if resp == nil {
|
|
return http.StatusNotFound, nil
|
|
}
|
|
if len(resp.Data) == 0 {
|
|
if len(resp.Warnings) > 0 {
|
|
return 0, nil
|
|
}
|
|
return http.StatusNotFound, nil
|
|
}
|
|
keysRaw, ok := resp.Data["keys"]
|
|
if !ok || keysRaw == nil {
|
|
// If we don't have keys but have other data, return as-is
|
|
if len(resp.Data) > 0 || len(resp.Warnings) > 0 {
|
|
return 0, nil
|
|
}
|
|
return http.StatusNotFound, nil
|
|
}
|
|
|
|
var keys []string
|
|
switch keysRaw.(type) {
|
|
case []interface{}:
|
|
keys = make([]string, len(keysRaw.([]interface{})))
|
|
for i, el := range keysRaw.([]interface{}) {
|
|
s, ok := el.(string)
|
|
if !ok {
|
|
return http.StatusInternalServerError, nil
|
|
}
|
|
keys[i] = s
|
|
}
|
|
|
|
case []string:
|
|
keys = keysRaw.([]string)
|
|
default:
|
|
return http.StatusInternalServerError, nil
|
|
}
|
|
|
|
if len(keys) == 0 {
|
|
return http.StatusNotFound, nil
|
|
}
|
|
}
|
|
|
|
return 0, nil
|
|
}
|
|
|
|
if errwrap.ContainsType(err, new(ReplicationCodedError)) {
|
|
var allErrors error
|
|
var codedErr *ReplicationCodedError
|
|
errwrap.Walk(err, func(inErr error) {
|
|
// The Walk function does not just traverse leaves, and execute the
|
|
// callback function on the entire error first. So, if the error is
|
|
// of type multierror.Error, we may want to skip storing the entire
|
|
// error first to avoid adding duplicate errors when walking down
|
|
// the leaf errors
|
|
if _, ok := inErr.(*multierror.Error); ok {
|
|
return
|
|
}
|
|
newErr, ok := inErr.(*ReplicationCodedError)
|
|
if ok {
|
|
codedErr = newErr
|
|
} else {
|
|
// if the error is of type fmt.wrapError which is typically
|
|
// made by calling fmt.Errorf("... %w", err), allErrors will
|
|
// contain duplicated error messages
|
|
allErrors = multierror.Append(allErrors, inErr)
|
|
}
|
|
})
|
|
if allErrors != nil {
|
|
return codedErr.Code, multierror.Append(fmt.Errorf("errors from both primary and secondary; primary error was %v; secondary errors follow", codedErr.Msg), allErrors)
|
|
}
|
|
return codedErr.Code, errors.New(codedErr.Msg)
|
|
}
|
|
|
|
// Start out with internal server error since in most of these cases there
|
|
// won't be a response so this won't be overridden
|
|
statusCode := http.StatusInternalServerError
|
|
// If we actually have a response, start out with bad request
|
|
if resp != nil {
|
|
statusCode = http.StatusBadRequest
|
|
}
|
|
|
|
// Now, check the error itself; if it has a specific logical error, set the
|
|
// appropriate code
|
|
if err != nil {
|
|
switch {
|
|
case errwrap.ContainsType(err, new(StatusBadRequest)):
|
|
statusCode = http.StatusBadRequest
|
|
case errwrap.Contains(err, ErrPermissionDenied.Error()):
|
|
statusCode = http.StatusForbidden
|
|
case errwrap.Contains(err, consts.ErrInvalidWrappingToken.Error()):
|
|
statusCode = http.StatusBadRequest
|
|
case errwrap.Contains(err, ErrUnsupportedOperation.Error()):
|
|
statusCode = http.StatusMethodNotAllowed
|
|
case errwrap.Contains(err, ErrUnsupportedPath.Error()):
|
|
statusCode = http.StatusNotFound
|
|
case errwrap.Contains(err, ErrInvalidRequest.Error()):
|
|
statusCode = http.StatusBadRequest
|
|
case errwrap.Contains(err, ErrUpstreamRateLimited.Error()):
|
|
statusCode = http.StatusBadGateway
|
|
case errwrap.Contains(err, ErrRateLimitQuotaExceeded.Error()):
|
|
statusCode = http.StatusTooManyRequests
|
|
case errwrap.Contains(err, ErrLeaseCountQuotaExceeded.Error()):
|
|
statusCode = http.StatusTooManyRequests
|
|
case errwrap.Contains(err, ErrMissingRequiredState.Error()):
|
|
statusCode = http.StatusPreconditionFailed
|
|
case errwrap.Contains(err, ErrPathFunctionalityRemoved.Error()):
|
|
statusCode = http.StatusNotFound
|
|
case errwrap.Contains(err, ErrRelativePath.Error()):
|
|
statusCode = http.StatusBadRequest
|
|
case errwrap.Contains(err, ErrInvalidCredentials.Error()):
|
|
statusCode = http.StatusBadRequest
|
|
}
|
|
}
|
|
|
|
if resp != nil && resp.IsError() {
|
|
err = fmt.Errorf("%s", resp.Data["error"].(string))
|
|
}
|
|
|
|
return statusCode, err
|
|
}
|
|
|
|
// AdjustErrorStatusCode adjusts the status that will be sent in error
|
|
// conditions in a way that can be shared across http's respondError and other
|
|
// locations.
|
|
func AdjustErrorStatusCode(status *int, err error) {
|
|
// Handle nested errors
|
|
if t, ok := err.(*multierror.Error); ok {
|
|
for _, e := range t.Errors {
|
|
AdjustErrorStatusCode(status, e)
|
|
}
|
|
}
|
|
|
|
// Adjust status code when sealed
|
|
if errwrap.Contains(err, consts.ErrSealed.Error()) {
|
|
*status = http.StatusServiceUnavailable
|
|
}
|
|
|
|
if errwrap.Contains(err, consts.ErrAPILocked.Error()) {
|
|
*status = http.StatusServiceUnavailable
|
|
}
|
|
|
|
// Adjust status code on
|
|
if errwrap.Contains(err, "http: request body too large") {
|
|
*status = http.StatusRequestEntityTooLarge
|
|
}
|
|
|
|
// Allow HTTPCoded error passthrough to specify a code
|
|
if t, ok := err.(HTTPCodedError); ok {
|
|
*status = t.Code()
|
|
}
|
|
}
|
|
|
|
func RespondError(w http.ResponseWriter, status int, err error) {
|
|
AdjustErrorStatusCode(&status, err)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(status)
|
|
|
|
type ErrorResponse struct {
|
|
Errors []string `json:"errors"`
|
|
}
|
|
resp := &ErrorResponse{Errors: make([]string, 0, 1)}
|
|
if err != nil {
|
|
resp.Errors = append(resp.Errors, err.Error())
|
|
}
|
|
|
|
enc := json.NewEncoder(w)
|
|
enc.Encode(resp)
|
|
}
|
|
|
|
func RespondErrorAndData(w http.ResponseWriter, status int, data interface{}, err error) {
|
|
AdjustErrorStatusCode(&status, err)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(status)
|
|
|
|
type ErrorAndDataResponse struct {
|
|
Errors []string `json:"errors"`
|
|
Data interface{} `json:"data""`
|
|
}
|
|
resp := &ErrorAndDataResponse{Errors: make([]string, 0, 1)}
|
|
if err != nil {
|
|
resp.Errors = append(resp.Errors, err.Error())
|
|
}
|
|
resp.Data = data
|
|
|
|
enc := json.NewEncoder(w)
|
|
enc.Encode(resp)
|
|
}
|