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