1package api
2
3import (
4	"bytes"
5	"encoding/json"
6	"io"
7	"io/ioutil"
8	"net/http"
9	"net/url"
10
11	"github.com/hashicorp/vault/sdk/helper/consts"
12
13	retryablehttp "github.com/hashicorp/go-retryablehttp"
14)
15
16// Request is a raw request configuration structure used to initiate
17// API requests to the Vault server.
18type Request struct {
19	Method        string
20	URL           *url.URL
21	Params        url.Values
22	Headers       http.Header
23	ClientToken   string
24	MFAHeaderVals []string
25	WrapTTL       string
26	Obj           interface{}
27
28	// When possible, use BodyBytes as it is more efficient due to how the
29	// retry logic works
30	BodyBytes []byte
31
32	// Fallback
33	Body     io.Reader
34	BodySize int64
35
36	// Whether to request overriding soft-mandatory Sentinel policies (RGPs and
37	// EGPs). If set, the override flag will take effect for all policies
38	// evaluated during the request.
39	PolicyOverride bool
40}
41
42// SetJSONBody is used to set a request body that is a JSON-encoded value.
43func (r *Request) SetJSONBody(val interface{}) error {
44	buf, err := json.Marshal(val)
45	if err != nil {
46		return err
47	}
48
49	r.Obj = val
50	r.BodyBytes = buf
51	return nil
52}
53
54// ResetJSONBody is used to reset the body for a redirect
55func (r *Request) ResetJSONBody() error {
56	if r.BodyBytes == nil {
57		return nil
58	}
59	return r.SetJSONBody(r.Obj)
60}
61
62// DEPRECATED: ToHTTP turns this request into a valid *http.Request for use
63// with the net/http package.
64func (r *Request) ToHTTP() (*http.Request, error) {
65	req, err := r.toRetryableHTTP()
66	if err != nil {
67		return nil, err
68	}
69
70	switch {
71	case r.BodyBytes == nil && r.Body == nil:
72		// No body
73
74	case r.BodyBytes != nil:
75		req.Request.Body = ioutil.NopCloser(bytes.NewReader(r.BodyBytes))
76
77	default:
78		if c, ok := r.Body.(io.ReadCloser); ok {
79			req.Request.Body = c
80		} else {
81			req.Request.Body = ioutil.NopCloser(r.Body)
82		}
83	}
84
85	return req.Request, nil
86}
87
88func (r *Request) toRetryableHTTP() (*retryablehttp.Request, error) {
89	// Encode the query parameters
90	r.URL.RawQuery = r.Params.Encode()
91
92	// Create the HTTP request, defaulting to retryable
93	var req *retryablehttp.Request
94
95	var err error
96	var body interface{}
97
98	switch {
99	case r.BodyBytes == nil && r.Body == nil:
100		// No body
101
102	case r.BodyBytes != nil:
103		// Use bytes, it's more efficient
104		body = r.BodyBytes
105
106	default:
107		body = r.Body
108	}
109
110	req, err = retryablehttp.NewRequest(r.Method, r.URL.RequestURI(), body)
111	if err != nil {
112		return nil, err
113	}
114
115	req.URL.User = r.URL.User
116	req.URL.Scheme = r.URL.Scheme
117	req.URL.Host = r.URL.Host
118	req.Host = r.URL.Host
119
120	if r.Headers != nil {
121		for header, vals := range r.Headers {
122			for _, val := range vals {
123				req.Header.Add(header, val)
124			}
125		}
126	}
127
128	if len(r.ClientToken) != 0 {
129		req.Header.Set(consts.AuthHeaderName, r.ClientToken)
130	}
131
132	if len(r.WrapTTL) != 0 {
133		req.Header.Set("X-Vault-Wrap-TTL", r.WrapTTL)
134	}
135
136	if len(r.MFAHeaderVals) != 0 {
137		for _, mfaHeaderVal := range r.MFAHeaderVals {
138			req.Header.Add("X-Vault-MFA", mfaHeaderVal)
139		}
140	}
141
142	if r.PolicyOverride {
143		req.Header.Set("X-Vault-Policy-Override", "true")
144	}
145
146	return req, nil
147}
148