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

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package logical
     5  
     6  import (
     7  	"bufio"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"net"
    12  	"net/http"
    13  	"strconv"
    14  	"sync/atomic"
    15  
    16  	"github.com/hashicorp/vault/sdk/helper/wrapping"
    17  )
    18  
    19  const (
    20  	// HTTPContentType can be specified in the Data field of a Response
    21  	// so that the HTTP front end can specify a custom Content-Type associated
    22  	// with the HTTPRawBody. This can only be used for non-secrets, and should
    23  	// be avoided unless absolutely necessary, such as implementing a specification.
    24  	// The value must be a string.
    25  	HTTPContentType = "http_content_type"
    26  
    27  	// HTTPRawBody is the raw content of the HTTP body that goes with the HTTPContentType.
    28  	// This can only be specified for non-secrets, and should should be similarly
    29  	// avoided like the HTTPContentType. The value must be a byte slice.
    30  	HTTPRawBody = "http_raw_body"
    31  
    32  	// HTTPStatusCode is the response code of the HTTP body that goes with the HTTPContentType.
    33  	// This can only be specified for non-secrets, and should should be similarly
    34  	// avoided like the HTTPContentType. The value must be an integer.
    35  	HTTPStatusCode = "http_status_code"
    36  
    37  	// For unwrapping we may need to know whether the value contained in the
    38  	// raw body is already JSON-unmarshaled. The presence of this key indicates
    39  	// that it has already been unmarshaled. That way we don't need to simply
    40  	// ignore errors.
    41  	HTTPRawBodyAlreadyJSONDecoded = "http_raw_body_already_json_decoded"
    42  
    43  	// If set, HTTPCacheControlHeader will replace the default Cache-Control=no-store header
    44  	// set by the generic wrapping handler. The value must be a string.
    45  	HTTPCacheControlHeader = "http_raw_cache_control"
    46  
    47  	// If set, HTTPPragmaHeader will set the Pragma response header.
    48  	// The value must be a string.
    49  	HTTPPragmaHeader = "http_raw_pragma"
    50  
    51  	// If set, HTTPWWWAuthenticateHeader will set the WWW-Authenticate response header.
    52  	// The value must be a string.
    53  	HTTPWWWAuthenticateHeader = "http_www_authenticate"
    54  )
    55  
    56  // Response is a struct that stores the response of a request.
    57  // It is used to abstract the details of the higher level request protocol.
    58  type Response struct {
    59  	// Secret, if not nil, denotes that this response represents a secret.
    60  	Secret *Secret `json:"secret" structs:"secret" mapstructure:"secret"`
    61  
    62  	// Auth, if not nil, contains the authentication information for
    63  	// this response. This is only checked and means something for
    64  	// credential backends.
    65  	Auth *Auth `json:"auth" structs:"auth" mapstructure:"auth"`
    66  
    67  	// Response data is an opaque map that must have string keys. For
    68  	// secrets, this data is sent down to the user as-is. To store internal
    69  	// data that you don't want the user to see, store it in
    70  	// Secret.InternalData.
    71  	Data map[string]interface{} `json:"data" structs:"data" mapstructure:"data"`
    72  
    73  	// Redirect is an HTTP URL to redirect to for further authentication.
    74  	// This is only valid for credential backends. This will be blanked
    75  	// for any logical backend and ignored.
    76  	Redirect string `json:"redirect" structs:"redirect" mapstructure:"redirect"`
    77  
    78  	// Warnings allow operations or backends to return warnings in response
    79  	// to user actions without failing the action outright.
    80  	Warnings []string `json:"warnings" structs:"warnings" mapstructure:"warnings"`
    81  
    82  	// Information for wrapping the response in a cubbyhole
    83  	WrapInfo *wrapping.ResponseWrapInfo `json:"wrap_info" structs:"wrap_info" mapstructure:"wrap_info"`
    84  
    85  	// Headers will contain the http headers from the plugin that it wishes to
    86  	// have as part of the output
    87  	Headers map[string][]string `json:"headers" structs:"headers" mapstructure:"headers"`
    88  
    89  	// MountType, if non-empty, provides some information about what kind
    90  	// of mount this secret came from.
    91  	MountType string `json:"mount_type" structs:"mount_type" mapstructure:"mount_type"`
    92  }
    93  
    94  // AddWarning adds a warning into the response's warning list
    95  func (r *Response) AddWarning(warning string) {
    96  	if r.Warnings == nil {
    97  		r.Warnings = make([]string, 0, 1)
    98  	}
    99  	r.Warnings = append(r.Warnings, warning)
   100  }
   101  
   102  // IsError returns true if this response seems to indicate an error.
   103  func (r *Response) IsError() bool {
   104  	// If the response data contains only an 'error' element, or an 'error' and a 'data' element only
   105  	return r != nil && r.Data != nil && r.Data["error"] != nil && (len(r.Data) == 1 || (r.Data["data"] != nil && len(r.Data) == 2))
   106  }
   107  
   108  func (r *Response) Error() error {
   109  	if !r.IsError() {
   110  		return nil
   111  	}
   112  	switch r.Data["error"].(type) {
   113  	case string:
   114  		return errors.New(r.Data["error"].(string))
   115  	case error:
   116  		return r.Data["error"].(error)
   117  	}
   118  	return nil
   119  }
   120  
   121  // HelpResponse is used to format a help response
   122  func HelpResponse(text string, seeAlso []string, oapiDoc interface{}) *Response {
   123  	return &Response{
   124  		Data: map[string]interface{}{
   125  			"help":     text,
   126  			"see_also": seeAlso,
   127  			"openapi":  oapiDoc,
   128  		},
   129  	}
   130  }
   131  
   132  // ErrorResponse is used to format an error response
   133  func ErrorResponse(text string, vargs ...interface{}) *Response {
   134  	if len(vargs) > 0 {
   135  		text = fmt.Sprintf(text, vargs...)
   136  	}
   137  	return &Response{
   138  		Data: map[string]interface{}{
   139  			"error": text,
   140  		},
   141  	}
   142  }
   143  
   144  // ListResponse is used to format a response to a list operation.
   145  func ListResponse(keys []string) *Response {
   146  	resp := &Response{
   147  		Data: map[string]interface{}{},
   148  	}
   149  	if len(keys) != 0 {
   150  		resp.Data["keys"] = keys
   151  	}
   152  	return resp
   153  }
   154  
   155  // ListResponseWithInfo is used to format a response to a list operation and
   156  // return the keys as well as a map with corresponding key info.
   157  func ListResponseWithInfo(keys []string, keyInfo map[string]interface{}) *Response {
   158  	resp := ListResponse(keys)
   159  
   160  	keyInfoData := make(map[string]interface{})
   161  	for _, key := range keys {
   162  		val, ok := keyInfo[key]
   163  		if ok {
   164  			keyInfoData[key] = val
   165  		}
   166  	}
   167  
   168  	if len(keyInfoData) > 0 {
   169  		resp.Data["key_info"] = keyInfoData
   170  	}
   171  
   172  	return resp
   173  }
   174  
   175  // RespondWithStatusCode takes a response and converts it to a raw response with
   176  // the provided Status Code.
   177  func RespondWithStatusCode(resp *Response, req *Request, code int) (*Response, error) {
   178  	ret := &Response{
   179  		Data: map[string]interface{}{
   180  			HTTPContentType: "application/json",
   181  			HTTPStatusCode:  code,
   182  		},
   183  	}
   184  
   185  	if resp != nil {
   186  		httpResp := LogicalResponseToHTTPResponse(resp)
   187  
   188  		if req != nil {
   189  			httpResp.RequestID = req.ID
   190  		}
   191  
   192  		body, err := json.Marshal(httpResp)
   193  		if err != nil {
   194  			return nil, err
   195  		}
   196  
   197  		// We default to string here so that the value is HMAC'd via audit.
   198  		// Since this function is always marshaling to JSON, this is
   199  		// appropriate.
   200  		ret.Data[HTTPRawBody] = string(body)
   201  	}
   202  
   203  	return ret, nil
   204  }
   205  
   206  // HTTPResponseWriter is optionally added to a request object and can be used to
   207  // write directly to the HTTP response writer.
   208  type HTTPResponseWriter struct {
   209  	http.ResponseWriter
   210  	written *uint32
   211  }
   212  
   213  // NewHTTPResponseWriter creates a new HTTPResponseWriter object that wraps the
   214  // provided io.Writer.
   215  func NewHTTPResponseWriter(w http.ResponseWriter) *HTTPResponseWriter {
   216  	return &HTTPResponseWriter{
   217  		ResponseWriter: w,
   218  		written:        new(uint32),
   219  	}
   220  }
   221  
   222  // Write will write the bytes to the underlying io.Writer.
   223  func (w *HTTPResponseWriter) Write(bytes []byte) (int, error) {
   224  	atomic.StoreUint32(w.written, 1)
   225  	return w.ResponseWriter.Write(bytes)
   226  }
   227  
   228  // Written tells us if the writer has been written to yet.
   229  func (w *HTTPResponseWriter) Written() bool {
   230  	return atomic.LoadUint32(w.written) == 1
   231  }
   232  
   233  type WrappingResponseWriter interface {
   234  	http.ResponseWriter
   235  	Wrapped() http.ResponseWriter
   236  }
   237  
   238  type StatusHeaderResponseWriter struct {
   239  	wrapped     http.ResponseWriter
   240  	wroteHeader bool
   241  	StatusCode  int
   242  	headers     map[string][]*CustomHeader
   243  }
   244  
   245  func NewStatusHeaderResponseWriter(w http.ResponseWriter, h map[string][]*CustomHeader) *StatusHeaderResponseWriter {
   246  	return &StatusHeaderResponseWriter{
   247  		wrapped:     w,
   248  		wroteHeader: false,
   249  		StatusCode:  200,
   250  		headers:     h,
   251  	}
   252  }
   253  
   254  func (w *StatusHeaderResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
   255  	if h, ok := w.wrapped.(http.Hijacker); ok {
   256  		return h.Hijack()
   257  	}
   258  	return nil, nil, fmt.Errorf("could not hijack because wrapped connection is %T and it does not implement http.Hijacker", w.wrapped)
   259  }
   260  
   261  func (w *StatusHeaderResponseWriter) Wrapped() http.ResponseWriter {
   262  	return w.wrapped
   263  }
   264  
   265  func (w *StatusHeaderResponseWriter) Header() http.Header {
   266  	return w.wrapped.Header()
   267  }
   268  
   269  func (w *StatusHeaderResponseWriter) Write(buf []byte) (int, error) {
   270  	// It is allowed to only call ResponseWriter.Write and skip
   271  	// ResponseWriter.WriteHeader. An example of such a situation is
   272  	// "handleUIStub". The Write function will internally set the status code
   273  	// 200 for the response for which that call might invoke other
   274  	// implementations of the WriteHeader function. So, we still need to set
   275  	// the custom headers. In cases where both WriteHeader and Write of
   276  	// statusHeaderResponseWriter struct are called the internal call to the
   277  	// WriterHeader invoked from inside Write method won't change the headers.
   278  	if !w.wroteHeader {
   279  		w.setCustomResponseHeaders(w.StatusCode)
   280  	}
   281  
   282  	return w.wrapped.Write(buf)
   283  }
   284  
   285  func (w *StatusHeaderResponseWriter) WriteHeader(statusCode int) {
   286  	w.setCustomResponseHeaders(statusCode)
   287  	w.wrapped.WriteHeader(statusCode)
   288  	w.StatusCode = statusCode
   289  	// in cases where Write is called after WriteHeader, let's prevent setting
   290  	// ResponseWriter headers twice
   291  	w.wroteHeader = true
   292  }
   293  
   294  func (w *StatusHeaderResponseWriter) setCustomResponseHeaders(status int) {
   295  	sch := w.headers
   296  	if sch == nil {
   297  		return
   298  	}
   299  
   300  	// Checking the validity of the status code
   301  	if status >= 600 || status < 100 {
   302  		return
   303  	}
   304  
   305  	// setter function to set the headers
   306  	setter := func(hvl []*CustomHeader) {
   307  		for _, hv := range hvl {
   308  			w.Header().Set(hv.Name, hv.Value)
   309  		}
   310  	}
   311  
   312  	// Setting the default headers first
   313  	setter(sch["default"])
   314  
   315  	// setting the Xyy pattern first
   316  	d := fmt.Sprintf("%vxx", status/100)
   317  	if val, ok := sch[d]; ok {
   318  		setter(val)
   319  	}
   320  
   321  	// Setting the specific headers
   322  	if val, ok := sch[strconv.Itoa(status)]; ok {
   323  		setter(val)
   324  	}
   325  
   326  	return
   327  }
   328  
   329  var _ WrappingResponseWriter = &StatusHeaderResponseWriter{}
   330  
   331  // ResolveRoleResponse returns a standard response to be returned by functions handling a ResolveRoleOperation
   332  func ResolveRoleResponse(roleName string) (*Response, error) {
   333  	return &Response{
   334  		Data: map[string]interface{}{
   335  			"role": roleName,
   336  		},
   337  	}, nil
   338  }