1package logical 2 3import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "io" 8 "sync/atomic" 9 10 "github.com/hashicorp/vault/sdk/helper/wrapping" 11) 12 13const ( 14 // HTTPContentType can be specified in the Data field of a Response 15 // so that the HTTP front end can specify a custom Content-Type associated 16 // with the HTTPRawBody. This can only be used for non-secrets, and should 17 // be avoided unless absolutely necessary, such as implementing a specification. 18 // The value must be a string. 19 HTTPContentType = "http_content_type" 20 21 // HTTPRawBody is the raw content of the HTTP body that goes with the HTTPContentType. 22 // This can only be specified for non-secrets, and should should be similarly 23 // avoided like the HTTPContentType. The value must be a byte slice. 24 HTTPRawBody = "http_raw_body" 25 26 // HTTPStatusCode is the response code of the HTTP body that goes with the HTTPContentType. 27 // This can only be specified for non-secrets, and should should be similarly 28 // avoided like the HTTPContentType. The value must be an integer. 29 HTTPStatusCode = "http_status_code" 30 31 // For unwrapping we may need to know whether the value contained in the 32 // raw body is already JSON-unmarshaled. The presence of this key indicates 33 // that it has already been unmarshaled. That way we don't need to simply 34 // ignore errors. 35 HTTPRawBodyAlreadyJSONDecoded = "http_raw_body_already_json_decoded" 36 37 // If set, HTTPRawCacheControl will replace the default Cache-Control=no-store header 38 // set by the generic wrapping handler. The value must be a string. 39 HTTPRawCacheControl = "http_raw_cache_control" 40) 41 42// Response is a struct that stores the response of a request. 43// It is used to abstract the details of the higher level request protocol. 44type Response struct { 45 // Secret, if not nil, denotes that this response represents a secret. 46 Secret *Secret `json:"secret" structs:"secret" mapstructure:"secret"` 47 48 // Auth, if not nil, contains the authentication information for 49 // this response. This is only checked and means something for 50 // credential backends. 51 Auth *Auth `json:"auth" structs:"auth" mapstructure:"auth"` 52 53 // Response data is an opaque map that must have string keys. For 54 // secrets, this data is sent down to the user as-is. To store internal 55 // data that you don't want the user to see, store it in 56 // Secret.InternalData. 57 Data map[string]interface{} `json:"data" structs:"data" mapstructure:"data"` 58 59 // Redirect is an HTTP URL to redirect to for further authentication. 60 // This is only valid for credential backends. This will be blanked 61 // for any logical backend and ignored. 62 Redirect string `json:"redirect" structs:"redirect" mapstructure:"redirect"` 63 64 // Warnings allow operations or backends to return warnings in response 65 // to user actions without failing the action outright. 66 Warnings []string `json:"warnings" structs:"warnings" mapstructure:"warnings"` 67 68 // Information for wrapping the response in a cubbyhole 69 WrapInfo *wrapping.ResponseWrapInfo `json:"wrap_info" structs:"wrap_info" mapstructure:"wrap_info"` 70 71 // Headers will contain the http headers from the plugin that it wishes to 72 // have as part of the output 73 Headers map[string][]string `json:"headers" structs:"headers" mapstructure:"headers"` 74} 75 76// AddWarning adds a warning into the response's warning list 77func (r *Response) AddWarning(warning string) { 78 if r.Warnings == nil { 79 r.Warnings = make([]string, 0, 1) 80 } 81 r.Warnings = append(r.Warnings, warning) 82} 83 84// IsError returns true if this response seems to indicate an error. 85func (r *Response) IsError() bool { 86 return r != nil && r.Data != nil && len(r.Data) == 1 && r.Data["error"] != nil 87} 88 89func (r *Response) Error() error { 90 if !r.IsError() { 91 return nil 92 } 93 switch r.Data["error"].(type) { 94 case string: 95 return errors.New(r.Data["error"].(string)) 96 case error: 97 return r.Data["error"].(error) 98 } 99 return nil 100} 101 102// HelpResponse is used to format a help response 103func HelpResponse(text string, seeAlso []string, oapiDoc interface{}) *Response { 104 return &Response{ 105 Data: map[string]interface{}{ 106 "help": text, 107 "see_also": seeAlso, 108 "openapi": oapiDoc, 109 }, 110 } 111} 112 113// ErrorResponse is used to format an error response 114func ErrorResponse(text string, vargs ...interface{}) *Response { 115 if len(vargs) > 0 { 116 text = fmt.Sprintf(text, vargs...) 117 } 118 return &Response{ 119 Data: map[string]interface{}{ 120 "error": text, 121 }, 122 } 123} 124 125// ListResponse is used to format a response to a list operation. 126func ListResponse(keys []string) *Response { 127 resp := &Response{ 128 Data: map[string]interface{}{}, 129 } 130 if len(keys) != 0 { 131 resp.Data["keys"] = keys 132 } 133 return resp 134} 135 136// ListResponseWithInfo is used to format a response to a list operation and 137// return the keys as well as a map with corresponding key info. 138func ListResponseWithInfo(keys []string, keyInfo map[string]interface{}) *Response { 139 resp := ListResponse(keys) 140 141 keyInfoData := make(map[string]interface{}) 142 for _, key := range keys { 143 val, ok := keyInfo[key] 144 if ok { 145 keyInfoData[key] = val 146 } 147 } 148 149 if len(keyInfoData) > 0 { 150 resp.Data["key_info"] = keyInfoData 151 } 152 153 return resp 154} 155 156// RespondWithStatusCode takes a response and converts it to a raw response with 157// the provided Status Code. 158func RespondWithStatusCode(resp *Response, req *Request, code int) (*Response, error) { 159 ret := &Response{ 160 Data: map[string]interface{}{ 161 HTTPContentType: "application/json", 162 HTTPStatusCode: code, 163 }, 164 } 165 166 if resp != nil { 167 httpResp := LogicalResponseToHTTPResponse(resp) 168 169 if req != nil { 170 httpResp.RequestID = req.ID 171 } 172 173 body, err := json.Marshal(httpResp) 174 if err != nil { 175 return nil, err 176 } 177 178 // We default to string here so that the value is HMAC'd via audit. 179 // Since this function is always marshaling to JSON, this is 180 // appropriate. 181 ret.Data[HTTPRawBody] = string(body) 182 } 183 184 return ret, nil 185} 186 187// HTTPResponseWriter is optionally added to a request object and can be used to 188// write directly to the HTTP response writter. 189type HTTPResponseWriter struct { 190 writer io.Writer 191 written *uint32 192} 193 194// NewHTTPResponseWriter creates a new HTTPRepoinseWriter object that wraps the 195// provided io.Writer. 196func NewHTTPResponseWriter(w io.Writer) *HTTPResponseWriter { 197 return &HTTPResponseWriter{ 198 writer: w, 199 written: new(uint32), 200 } 201} 202 203// Write will write the bytes to the underlying io.Writer. 204func (rw *HTTPResponseWriter) Write(bytes []byte) (int, error) { 205 atomic.StoreUint32(rw.written, 1) 206 207 return rw.writer.Write(bytes) 208} 209 210// Written tells us if the writer has been written to yet. 211func (rw *HTTPResponseWriter) Written() bool { 212 return atomic.LoadUint32(rw.written) == 1 213} 214