2017-02-16 20:15:02 +00:00
package logical
import (
2019-03-05 16:43:30 +00:00
"encoding/json"
2017-02-16 20:15:02 +00:00
"errors"
"fmt"
"net/http"
"github.com/hashicorp/errwrap"
multierror "github.com/hashicorp/go-multierror"
2019-04-12 21:54:35 +00:00
"github.com/hashicorp/vault/sdk/helper/consts"
2017-02-16 20:15:02 +00:00
)
// 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 :
if resp == nil {
return http . StatusNotFound , nil
}
// Basically: if we have empty "keys" or no keys at all, 404. This
// provides consistency with GET.
2018-12-03 17:18:28 +00:00
case req . Operation == ListOperation && ( resp == nil || resp . WrapInfo == nil ) :
2018-04-04 02:35:45 +00:00
if resp == nil {
return http . StatusNotFound , nil
}
if len ( resp . Data ) == 0 {
if len ( resp . Warnings ) > 0 {
return 0 , nil
}
2017-02-16 20:15:02 +00:00
return http . StatusNotFound , nil
}
keysRaw , ok := resp . Data [ "keys" ]
if ! ok || keysRaw == nil {
2018-04-04 02:35:45 +00:00
// 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
}
2017-02-16 20:15:02 +00:00
return http . StatusNotFound , nil
}
2018-02-01 22:30:17 +00:00
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 :
2017-02-16 20:15:02 +00:00
return http . StatusInternalServerError , nil
}
2018-02-01 22:30:17 +00:00
2017-02-16 20:15:02 +00:00
if len ( keys ) == 0 {
return http . StatusNotFound , nil
}
}
return 0 , nil
}
if errwrap . ContainsType ( err , new ( ReplicationCodedError ) ) {
var allErrors error
2018-09-18 03:03:00 +00:00
var codedErr * ReplicationCodedError
2017-02-16 20:15:02 +00:00
errwrap . Walk ( err , func ( inErr error ) {
newErr , ok := inErr . ( * ReplicationCodedError )
2018-09-18 03:03:00 +00:00
if ok {
codedErr = newErr
} else {
allErrors = multierror . Append ( allErrors , inErr )
2017-02-16 20:15:02 +00:00
}
} )
if allErrors != nil {
2020-06-26 21:13:16 +00:00
return codedErr . Code , multierror . Append ( fmt . Errorf ( "errors from both primary and secondary; primary error was %v; secondary errors follow" , codedErr . Msg ) , allErrors )
2017-02-16 20:15:02 +00:00
}
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 , ErrUnsupportedOperation . Error ( ) ) :
statusCode = http . StatusMethodNotAllowed
case errwrap . Contains ( err , ErrUnsupportedPath . Error ( ) ) :
statusCode = http . StatusNotFound
case errwrap . Contains ( err , ErrInvalidRequest . Error ( ) ) :
statusCode = http . StatusBadRequest
2018-08-28 20:59:02 +00:00
case errwrap . Contains ( err , ErrUpstreamRateLimited . Error ( ) ) :
statusCode = http . StatusBadGateway
2020-06-26 21:13:16 +00:00
case errwrap . Contains ( err , ErrRateLimitQuotaExceeded . Error ( ) ) :
statusCode = http . StatusTooManyRequests
case errwrap . Contains ( err , ErrLeaseCountQuotaExceeded . Error ( ) ) :
statusCode = http . StatusTooManyRequests
2017-02-16 20:15:02 +00:00
}
}
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 ) {
2018-07-09 20:08:44 +00:00
// Handle nested errors
if t , ok := err . ( * multierror . Error ) ; ok {
for _ , e := range t . Errors {
AdjustErrorStatusCode ( status , e )
}
}
2017-02-16 20:15:02 +00:00
// Adjust status code when sealed
if errwrap . Contains ( err , consts . ErrSealed . 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 ( )
}
}
2019-03-05 16:43:30 +00:00
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 )
}