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