/
response.go
312 lines (260 loc) · 9.2 KB
/
response.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
package logical
import (
"encoding/json"
"errors"
"fmt"
"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"`
}
// 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 {
return r != nil && r.Data != nil && len(r.Data) == 1 && r.Data["error"] != nil
}
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,
},
}
}
// 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) 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{}