github.com/hashicorp/vault/sdk@v0.13.0/logical/response_util.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package logical
     5  
     6  import (
     7  	"encoding/json"
     8  	"errors"
     9  	"fmt"
    10  	"net/http"
    11  	"strings"
    12  
    13  	"github.com/hashicorp/errwrap"
    14  	multierror "github.com/hashicorp/go-multierror"
    15  	"github.com/hashicorp/vault/sdk/helper/consts"
    16  )
    17  
    18  // RespondErrorCommon pulls most of the functionality from http's
    19  // respondErrorCommon and some of http's handleLogical and makes it available
    20  // to both the http package and elsewhere.
    21  func RespondErrorCommon(req *Request, resp *Response, err error) (int, error) {
    22  	if err == nil && (resp == nil || !resp.IsError()) {
    23  		switch {
    24  		case req.Operation == ReadOperation || req.Operation == HeaderOperation:
    25  			if resp == nil {
    26  				return http.StatusNotFound, nil
    27  			}
    28  
    29  		// Basically: if we have empty "keys" or no keys at all, 404. This
    30  		// provides consistency with GET.
    31  		case req.Operation == ListOperation && (resp == nil || resp.WrapInfo == nil):
    32  			if resp == nil {
    33  				return http.StatusNotFound, nil
    34  			}
    35  			if len(resp.Data) == 0 {
    36  				if len(resp.Warnings) > 0 {
    37  					return 0, nil
    38  				}
    39  				return http.StatusNotFound, nil
    40  			}
    41  			keysRaw, ok := resp.Data["keys"]
    42  			if !ok || keysRaw == nil {
    43  				// If we don't have keys but have other data, return as-is
    44  				if len(resp.Data) > 0 || len(resp.Warnings) > 0 {
    45  					return 0, nil
    46  				}
    47  				return http.StatusNotFound, nil
    48  			}
    49  
    50  			var keys []string
    51  			switch keysRaw.(type) {
    52  			case []interface{}:
    53  				keys = make([]string, len(keysRaw.([]interface{})))
    54  				for i, el := range keysRaw.([]interface{}) {
    55  					s, ok := el.(string)
    56  					if !ok {
    57  						return http.StatusInternalServerError, nil
    58  					}
    59  					keys[i] = s
    60  				}
    61  
    62  			case []string:
    63  				keys = keysRaw.([]string)
    64  			default:
    65  				return http.StatusInternalServerError, nil
    66  			}
    67  
    68  			if len(keys) == 0 {
    69  				return http.StatusNotFound, nil
    70  			}
    71  		}
    72  
    73  		return 0, nil
    74  	}
    75  
    76  	if errwrap.ContainsType(err, new(ReplicationCodedError)) {
    77  		var allErrors error
    78  		var codedErr *ReplicationCodedError
    79  		errwrap.Walk(err, func(inErr error) {
    80  			// The Walk function does not just traverse leaves, and execute the
    81  			// callback function on the entire error first. So, if the error is
    82  			// of type multierror.Error, we may want to skip storing the entire
    83  			// error first to avoid adding duplicate errors when walking down
    84  			// the leaf errors
    85  			if _, ok := inErr.(*multierror.Error); ok {
    86  				return
    87  			}
    88  			newErr, ok := inErr.(*ReplicationCodedError)
    89  			if ok {
    90  				codedErr = newErr
    91  			} else {
    92  				// if the error is of type fmt.wrapError which is typically
    93  				// made by calling fmt.Errorf("... %w", err), allErrors will
    94  				// contain duplicated error messages
    95  				allErrors = multierror.Append(allErrors, inErr)
    96  			}
    97  		})
    98  		if allErrors != nil {
    99  			return codedErr.Code, multierror.Append(fmt.Errorf("errors from both primary and secondary; primary error was %v; secondary errors follow", codedErr.Msg), allErrors)
   100  		}
   101  		return codedErr.Code, errors.New(codedErr.Msg)
   102  	}
   103  
   104  	// Start out with internal server error since in most of these cases there
   105  	// won't be a response so this won't be overridden
   106  	statusCode := http.StatusInternalServerError
   107  	// If we actually have a response, start out with bad request
   108  	if resp != nil {
   109  		statusCode = http.StatusBadRequest
   110  	}
   111  
   112  	// Now, check the error itself; if it has a specific logical error, set the
   113  	// appropriate code
   114  	if err != nil {
   115  		switch {
   116  		case errwrap.Contains(err, consts.ErrOverloaded.Error()):
   117  			statusCode = http.StatusServiceUnavailable
   118  		case errwrap.ContainsType(err, new(StatusBadRequest)):
   119  			statusCode = http.StatusBadRequest
   120  		case errwrap.Contains(err, ErrPermissionDenied.Error()):
   121  			statusCode = http.StatusForbidden
   122  		case errwrap.Contains(err, consts.ErrInvalidWrappingToken.Error()):
   123  			statusCode = http.StatusBadRequest
   124  		case errwrap.Contains(err, ErrUnsupportedOperation.Error()):
   125  			statusCode = http.StatusMethodNotAllowed
   126  		case errwrap.Contains(err, ErrUnsupportedPath.Error()):
   127  			statusCode = http.StatusNotFound
   128  		case errwrap.Contains(err, ErrInvalidRequest.Error()):
   129  			statusCode = http.StatusBadRequest
   130  		case errwrap.Contains(err, ErrUpstreamRateLimited.Error()):
   131  			statusCode = http.StatusBadGateway
   132  		case errwrap.Contains(err, ErrRateLimitQuotaExceeded.Error()):
   133  			statusCode = http.StatusTooManyRequests
   134  		case errwrap.Contains(err, ErrLeaseCountQuotaExceeded.Error()):
   135  			statusCode = http.StatusTooManyRequests
   136  		case errwrap.Contains(err, ErrMissingRequiredState.Error()):
   137  			statusCode = http.StatusPreconditionFailed
   138  		case errwrap.Contains(err, ErrPathFunctionalityRemoved.Error()):
   139  			statusCode = http.StatusNotFound
   140  		case errwrap.Contains(err, ErrRelativePath.Error()):
   141  			statusCode = http.StatusBadRequest
   142  		case errwrap.Contains(err, ErrInvalidCredentials.Error()):
   143  			statusCode = http.StatusBadRequest
   144  		case errors.Is(err, ErrNotFound):
   145  			statusCode = http.StatusNotFound
   146  		}
   147  	}
   148  
   149  	if respErr := resp.Error(); respErr != nil {
   150  		err = fmt.Errorf("%s", respErr.Error())
   151  
   152  		// Don't let other error codes override the overloaded status code
   153  		if strings.Contains(respErr.Error(), consts.ErrOverloaded.Error()) {
   154  			statusCode = http.StatusServiceUnavailable
   155  		}
   156  	}
   157  
   158  	return statusCode, err
   159  }
   160  
   161  // AdjustErrorStatusCode adjusts the status that will be sent in error
   162  // conditions in a way that can be shared across http's respondError and other
   163  // locations.
   164  func AdjustErrorStatusCode(status *int, err error) {
   165  	// Handle nested errors
   166  	if t, ok := err.(*multierror.Error); ok {
   167  		for _, e := range t.Errors {
   168  			AdjustErrorStatusCode(status, e)
   169  		}
   170  	}
   171  
   172  	// Adjust status code when overloaded
   173  	if errwrap.Contains(err, consts.ErrOverloaded.Error()) {
   174  		*status = http.StatusServiceUnavailable
   175  	}
   176  
   177  	// Adjust status code when sealed
   178  	if errwrap.Contains(err, consts.ErrSealed.Error()) {
   179  		*status = http.StatusServiceUnavailable
   180  	}
   181  
   182  	if errwrap.Contains(err, consts.ErrAPILocked.Error()) {
   183  		*status = http.StatusServiceUnavailable
   184  	}
   185  
   186  	// Adjust status code on
   187  	if errwrap.Contains(err, "http: request body too large") {
   188  		*status = http.StatusRequestEntityTooLarge
   189  	}
   190  
   191  	// Allow HTTPCoded error passthrough to specify a code
   192  	if t, ok := err.(HTTPCodedError); ok {
   193  		*status = t.Code()
   194  	}
   195  }
   196  
   197  func RespondError(w http.ResponseWriter, status int, err error) {
   198  	AdjustErrorStatusCode(&status, err)
   199  
   200  	w.Header().Set("Content-Type", "application/json")
   201  	w.WriteHeader(status)
   202  
   203  	type ErrorResponse struct {
   204  		Errors []string `json:"errors"`
   205  	}
   206  	resp := &ErrorResponse{Errors: make([]string, 0, 1)}
   207  	if err != nil {
   208  		resp.Errors = append(resp.Errors, err.Error())
   209  	}
   210  
   211  	enc := json.NewEncoder(w)
   212  	enc.Encode(resp)
   213  }
   214  
   215  func RespondErrorAndData(w http.ResponseWriter, status int, data interface{}, err error) {
   216  	AdjustErrorStatusCode(&status, err)
   217  
   218  	w.Header().Set("Content-Type", "application/json")
   219  	w.WriteHeader(status)
   220  
   221  	type ErrorAndDataResponse struct {
   222  		Errors []string    `json:"errors"`
   223  		Data   interface{} `json:"data"`
   224  	}
   225  	resp := &ErrorAndDataResponse{Errors: make([]string, 0, 1)}
   226  	if err != nil {
   227  		resp.Errors = append(resp.Errors, err.Error())
   228  	}
   229  	resp.Data = data
   230  
   231  	enc := json.NewEncoder(w)
   232  	enc.Encode(resp)
   233  }