mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-11-03 20:17:59 +00:00 
			
		
		
		
	* Add an option to allow cert-auth to return metadata about client certs that fail login * Add cl * Update SPDX header for sdk/logical/response_test.go
		
			
				
	
	
		
			348 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			348 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright (c) HashiCorp, Inc.
 | 
						|
// SPDX-License-Identifier: MPL-2.0
 | 
						|
 | 
						|
package logical
 | 
						|
 | 
						|
import (
 | 
						|
	"bufio"
 | 
						|
	"encoding/json"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"net"
 | 
						|
	"net/http"
 | 
						|
	"strconv"
 | 
						|
	"sync/atomic"
 | 
						|
 | 
						|
	"github.com/hashicorp/vault/sdk/helper/wrapping"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	// HTTPContentType can be specified in the Data field of a Response
 | 
						|
	// so that the HTTP front end can specify a custom Content-Type associated
 | 
						|
	// with the HTTPRawBody. This can only be used for non-secrets, and should
 | 
						|
	// be avoided unless absolutely necessary, such as implementing a specification.
 | 
						|
	// The value must be a string.
 | 
						|
	HTTPContentType = "http_content_type"
 | 
						|
 | 
						|
	// HTTPRawBody is the raw content of the HTTP body that goes with the HTTPContentType.
 | 
						|
	// This can only be specified for non-secrets, and should should be similarly
 | 
						|
	// avoided like the HTTPContentType. The value must be a byte slice.
 | 
						|
	HTTPRawBody = "http_raw_body"
 | 
						|
 | 
						|
	// HTTPStatusCode is the response code of the HTTP body that goes with the HTTPContentType.
 | 
						|
	// This can only be specified for non-secrets, and should should be similarly
 | 
						|
	// avoided like the HTTPContentType. The value must be an integer.
 | 
						|
	HTTPStatusCode = "http_status_code"
 | 
						|
 | 
						|
	// For unwrapping we may need to know whether the value contained in the
 | 
						|
	// raw body is already JSON-unmarshaled. The presence of this key indicates
 | 
						|
	// that it has already been unmarshaled. That way we don't need to simply
 | 
						|
	// ignore errors.
 | 
						|
	HTTPRawBodyAlreadyJSONDecoded = "http_raw_body_already_json_decoded"
 | 
						|
 | 
						|
	// If set, HTTPCacheControlHeader will replace the default Cache-Control=no-store header
 | 
						|
	// set by the generic wrapping handler. The value must be a string.
 | 
						|
	HTTPCacheControlHeader = "http_raw_cache_control"
 | 
						|
 | 
						|
	// If set, HTTPPragmaHeader will set the Pragma response header.
 | 
						|
	// The value must be a string.
 | 
						|
	HTTPPragmaHeader = "http_raw_pragma"
 | 
						|
 | 
						|
	// If set, HTTPWWWAuthenticateHeader will set the WWW-Authenticate response header.
 | 
						|
	// The value must be a string.
 | 
						|
	HTTPWWWAuthenticateHeader = "http_www_authenticate"
 | 
						|
)
 | 
						|
 | 
						|
// Response is a struct that stores the response of a request.
 | 
						|
// It is used to abstract the details of the higher level request protocol.
 | 
						|
type Response struct {
 | 
						|
	// Secret, if not nil, denotes that this response represents a secret.
 | 
						|
	Secret *Secret `json:"secret" structs:"secret" mapstructure:"secret"`
 | 
						|
 | 
						|
	// Auth, if not nil, contains the authentication information for
 | 
						|
	// this response. This is only checked and means something for
 | 
						|
	// credential backends.
 | 
						|
	Auth *Auth `json:"auth" structs:"auth" mapstructure:"auth"`
 | 
						|
 | 
						|
	// Response data is an opaque map that must have string keys. For
 | 
						|
	// secrets, this data is sent down to the user as-is. To store internal
 | 
						|
	// data that you don't want the user to see, store it in
 | 
						|
	// Secret.InternalData.
 | 
						|
	Data map[string]interface{} `json:"data" structs:"data" mapstructure:"data"`
 | 
						|
 | 
						|
	// Redirect is an HTTP URL to redirect to for further authentication.
 | 
						|
	// This is only valid for credential backends. This will be blanked
 | 
						|
	// for any logical backend and ignored.
 | 
						|
	Redirect string `json:"redirect" structs:"redirect" mapstructure:"redirect"`
 | 
						|
 | 
						|
	// Warnings allow operations or backends to return warnings in response
 | 
						|
	// to user actions without failing the action outright.
 | 
						|
	Warnings []string `json:"warnings" structs:"warnings" mapstructure:"warnings"`
 | 
						|
 | 
						|
	// Information for wrapping the response in a cubbyhole
 | 
						|
	WrapInfo *wrapping.ResponseWrapInfo `json:"wrap_info" structs:"wrap_info" mapstructure:"wrap_info"`
 | 
						|
 | 
						|
	// Headers will contain the http headers from the plugin that it wishes to
 | 
						|
	// have as part of the output
 | 
						|
	Headers map[string][]string `json:"headers" structs:"headers" mapstructure:"headers"`
 | 
						|
 | 
						|
	// MountType, if non-empty, provides some information about what kind
 | 
						|
	// of mount this secret came from.
 | 
						|
	MountType string `json:"mount_type" structs:"mount_type" mapstructure:"mount_type"`
 | 
						|
}
 | 
						|
 | 
						|
// AddWarning adds a warning into the response's warning list
 | 
						|
func (r *Response) AddWarning(warning string) {
 | 
						|
	if r.Warnings == nil {
 | 
						|
		r.Warnings = make([]string, 0, 1)
 | 
						|
	}
 | 
						|
	r.Warnings = append(r.Warnings, warning)
 | 
						|
}
 | 
						|
 | 
						|
// IsError returns true if this response seems to indicate an error.
 | 
						|
func (r *Response) IsError() bool {
 | 
						|
	// If the response data contains only an 'error' element, or an 'error' and a 'data' element only
 | 
						|
	return r != nil && r.Data != nil && r.Data["error"] != nil && (len(r.Data) == 1 || (r.Data["data"] != nil && len(r.Data) == 2))
 | 
						|
}
 | 
						|
 | 
						|
func (r *Response) Error() error {
 | 
						|
	if !r.IsError() {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	switch r.Data["error"].(type) {
 | 
						|
	case string:
 | 
						|
		return errors.New(r.Data["error"].(string))
 | 
						|
	case error:
 | 
						|
		return r.Data["error"].(error)
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// HelpResponse is used to format a help response
 | 
						|
func HelpResponse(text string, seeAlso []string, oapiDoc interface{}) *Response {
 | 
						|
	return &Response{
 | 
						|
		Data: map[string]interface{}{
 | 
						|
			"help":     text,
 | 
						|
			"see_also": seeAlso,
 | 
						|
			"openapi":  oapiDoc,
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// ErrorResponse is used to format an error response
 | 
						|
func ErrorResponse(text string, vargs ...interface{}) *Response {
 | 
						|
	if len(vargs) > 0 {
 | 
						|
		text = fmt.Sprintf(text, vargs...)
 | 
						|
	}
 | 
						|
	return &Response{
 | 
						|
		Data: map[string]interface{}{
 | 
						|
			"error": text,
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// ErrorResponseWithData is used to format an error response with additional data returned
 | 
						|
// within the "data" sub-field of the Data field. Useful to return additional information to the client
 | 
						|
// and or appear within audited responses.
 | 
						|
func ErrorResponseWithData(data interface{}, text string, vargs ...interface{}) *Response {
 | 
						|
	resp := ErrorResponse(text, vargs...)
 | 
						|
	resp.Data["data"] = data
 | 
						|
	return resp
 | 
						|
}
 | 
						|
 | 
						|
// ListResponse is used to format a response to a list operation.
 | 
						|
func ListResponse(keys []string) *Response {
 | 
						|
	resp := &Response{
 | 
						|
		Data: map[string]interface{}{},
 | 
						|
	}
 | 
						|
	if len(keys) != 0 {
 | 
						|
		resp.Data["keys"] = keys
 | 
						|
	}
 | 
						|
	return resp
 | 
						|
}
 | 
						|
 | 
						|
// ListResponseWithInfo is used to format a response to a list operation and
 | 
						|
// return the keys as well as a map with corresponding key info.
 | 
						|
func ListResponseWithInfo(keys []string, keyInfo map[string]interface{}) *Response {
 | 
						|
	resp := ListResponse(keys)
 | 
						|
 | 
						|
	keyInfoData := make(map[string]interface{})
 | 
						|
	for _, key := range keys {
 | 
						|
		val, ok := keyInfo[key]
 | 
						|
		if ok {
 | 
						|
			keyInfoData[key] = val
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if len(keyInfoData) > 0 {
 | 
						|
		resp.Data["key_info"] = keyInfoData
 | 
						|
	}
 | 
						|
 | 
						|
	return resp
 | 
						|
}
 | 
						|
 | 
						|
// RespondWithStatusCode takes a response and converts it to a raw response with
 | 
						|
// the provided Status Code.
 | 
						|
func RespondWithStatusCode(resp *Response, req *Request, code int) (*Response, error) {
 | 
						|
	ret := &Response{
 | 
						|
		Data: map[string]interface{}{
 | 
						|
			HTTPContentType: "application/json",
 | 
						|
			HTTPStatusCode:  code,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	if resp != nil {
 | 
						|
		httpResp := LogicalResponseToHTTPResponse(resp)
 | 
						|
 | 
						|
		if req != nil {
 | 
						|
			httpResp.RequestID = req.ID
 | 
						|
		}
 | 
						|
 | 
						|
		body, err := json.Marshal(httpResp)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
 | 
						|
		// We default to string here so that the value is HMAC'd via audit.
 | 
						|
		// Since this function is always marshaling to JSON, this is
 | 
						|
		// appropriate.
 | 
						|
		ret.Data[HTTPRawBody] = string(body)
 | 
						|
	}
 | 
						|
 | 
						|
	return ret, nil
 | 
						|
}
 | 
						|
 | 
						|
// HTTPResponseWriter is optionally added to a request object and can be used to
 | 
						|
// write directly to the HTTP response writer.
 | 
						|
type HTTPResponseWriter struct {
 | 
						|
	http.ResponseWriter
 | 
						|
	written *uint32
 | 
						|
}
 | 
						|
 | 
						|
// NewHTTPResponseWriter creates a new HTTPResponseWriter object that wraps the
 | 
						|
// provided io.Writer.
 | 
						|
func NewHTTPResponseWriter(w http.ResponseWriter) *HTTPResponseWriter {
 | 
						|
	return &HTTPResponseWriter{
 | 
						|
		ResponseWriter: w,
 | 
						|
		written:        new(uint32),
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Write will write the bytes to the underlying io.Writer.
 | 
						|
func (w *HTTPResponseWriter) Write(bytes []byte) (int, error) {
 | 
						|
	atomic.StoreUint32(w.written, 1)
 | 
						|
	return w.ResponseWriter.Write(bytes)
 | 
						|
}
 | 
						|
 | 
						|
// Written tells us if the writer has been written to yet.
 | 
						|
func (w *HTTPResponseWriter) Written() bool {
 | 
						|
	return atomic.LoadUint32(w.written) == 1
 | 
						|
}
 | 
						|
 | 
						|
type WrappingResponseWriter interface {
 | 
						|
	http.ResponseWriter
 | 
						|
	Wrapped() http.ResponseWriter
 | 
						|
}
 | 
						|
 | 
						|
type StatusHeaderResponseWriter struct {
 | 
						|
	wrapped     http.ResponseWriter
 | 
						|
	wroteHeader bool
 | 
						|
	StatusCode  int
 | 
						|
	headers     map[string][]*CustomHeader
 | 
						|
}
 | 
						|
 | 
						|
func NewStatusHeaderResponseWriter(w http.ResponseWriter, h map[string][]*CustomHeader) *StatusHeaderResponseWriter {
 | 
						|
	return &StatusHeaderResponseWriter{
 | 
						|
		wrapped:     w,
 | 
						|
		wroteHeader: false,
 | 
						|
		StatusCode:  200,
 | 
						|
		headers:     h,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (w *StatusHeaderResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
 | 
						|
	if h, ok := w.wrapped.(http.Hijacker); ok {
 | 
						|
		return h.Hijack()
 | 
						|
	}
 | 
						|
	return nil, nil, fmt.Errorf("could not hijack because wrapped connection is %T and it does not implement http.Hijacker", w.wrapped)
 | 
						|
}
 | 
						|
 | 
						|
func (w *StatusHeaderResponseWriter) Wrapped() http.ResponseWriter {
 | 
						|
	return w.wrapped
 | 
						|
}
 | 
						|
 | 
						|
func (w *StatusHeaderResponseWriter) Header() http.Header {
 | 
						|
	return w.wrapped.Header()
 | 
						|
}
 | 
						|
 | 
						|
func (w *StatusHeaderResponseWriter) Write(buf []byte) (int, error) {
 | 
						|
	// It is allowed to only call ResponseWriter.Write and skip
 | 
						|
	// ResponseWriter.WriteHeader. An example of such a situation is
 | 
						|
	// "handleUIStub". The Write function will internally set the status code
 | 
						|
	// 200 for the response for which that call might invoke other
 | 
						|
	// implementations of the WriteHeader function. So, we still need to set
 | 
						|
	// the custom headers. In cases where both WriteHeader and Write of
 | 
						|
	// statusHeaderResponseWriter struct are called the internal call to the
 | 
						|
	// WriterHeader invoked from inside Write method won't change the headers.
 | 
						|
	if !w.wroteHeader {
 | 
						|
		w.setCustomResponseHeaders(w.StatusCode)
 | 
						|
	}
 | 
						|
 | 
						|
	return w.wrapped.Write(buf)
 | 
						|
}
 | 
						|
 | 
						|
func (w *StatusHeaderResponseWriter) WriteHeader(statusCode int) {
 | 
						|
	w.setCustomResponseHeaders(statusCode)
 | 
						|
	w.wrapped.WriteHeader(statusCode)
 | 
						|
	w.StatusCode = statusCode
 | 
						|
	// in cases where Write is called after WriteHeader, let's prevent setting
 | 
						|
	// ResponseWriter headers twice
 | 
						|
	w.wroteHeader = true
 | 
						|
}
 | 
						|
 | 
						|
func (w *StatusHeaderResponseWriter) setCustomResponseHeaders(status int) {
 | 
						|
	sch := w.headers
 | 
						|
	if sch == nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// Checking the validity of the status code
 | 
						|
	if status >= 600 || status < 100 {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// setter function to set the headers
 | 
						|
	setter := func(hvl []*CustomHeader) {
 | 
						|
		for _, hv := range hvl {
 | 
						|
			w.Header().Set(hv.Name, hv.Value)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Setting the default headers first
 | 
						|
	setter(sch["default"])
 | 
						|
 | 
						|
	// setting the Xyy pattern first
 | 
						|
	d := fmt.Sprintf("%vxx", status/100)
 | 
						|
	if val, ok := sch[d]; ok {
 | 
						|
		setter(val)
 | 
						|
	}
 | 
						|
 | 
						|
	// Setting the specific headers
 | 
						|
	if val, ok := sch[strconv.Itoa(status)]; ok {
 | 
						|
		setter(val)
 | 
						|
	}
 | 
						|
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
var _ WrappingResponseWriter = &StatusHeaderResponseWriter{}
 | 
						|
 | 
						|
// ResolveRoleResponse returns a standard response to be returned by functions handling a ResolveRoleOperation
 | 
						|
func ResolveRoleResponse(roleName string) (*Response, error) {
 | 
						|
	return &Response{
 | 
						|
		Data: map[string]interface{}{
 | 
						|
			"role": roleName,
 | 
						|
		},
 | 
						|
	}, nil
 | 
						|
}
 |