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 }