Merge pull request #266 from hashicorp/f-http
Support for HTTP Response overwriting
This commit is contained in:
commit
4946a66e30
|
@ -96,6 +96,12 @@ func respondLogical(w http.ResponseWriter, r *http.Request, path string, resp *l
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this is a raw response
|
||||||
|
if _, ok := resp.Data[logical.HTTPContentType]; ok {
|
||||||
|
respondRaw(w, r, path, resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
logicalResp := &LogicalResponse{Data: resp.Data}
|
logicalResp := &LogicalResponse{Data: resp.Data}
|
||||||
if resp.Secret != nil {
|
if resp.Secret != nil {
|
||||||
logicalResp.LeaseID = resp.Secret.LeaseID
|
logicalResp.LeaseID = resp.Secret.LeaseID
|
||||||
|
@ -140,6 +146,58 @@ func respondLogical(w http.ResponseWriter, r *http.Request, path string, resp *l
|
||||||
respondOk(w, httpResp)
|
respondOk(w, httpResp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// respondRaw is used when the response is using HTTPContentType and HTTPRawBody
|
||||||
|
// to change the default response handling. This is only used for specific things like
|
||||||
|
// returning the CRL information on the PKI backends.
|
||||||
|
func respondRaw(w http.ResponseWriter, r *http.Request, path string, resp *logical.Response) {
|
||||||
|
// Ensure this is never a secret or auth response
|
||||||
|
if resp.Secret != nil || resp.Auth != nil {
|
||||||
|
respondError(w, http.StatusInternalServerError, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the status code
|
||||||
|
statusRaw, ok := resp.Data[logical.HTTPStatusCode]
|
||||||
|
if !ok {
|
||||||
|
respondError(w, http.StatusInternalServerError, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
status, ok := statusRaw.(int)
|
||||||
|
if !ok {
|
||||||
|
respondError(w, http.StatusInternalServerError, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the header
|
||||||
|
contentTypeRaw, ok := resp.Data[logical.HTTPContentType]
|
||||||
|
if !ok {
|
||||||
|
respondError(w, http.StatusInternalServerError, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
contentType, ok := contentTypeRaw.(string)
|
||||||
|
if !ok {
|
||||||
|
respondError(w, http.StatusInternalServerError, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the body
|
||||||
|
bodyRaw, ok := resp.Data[logical.HTTPRawBody]
|
||||||
|
if !ok {
|
||||||
|
respondError(w, http.StatusInternalServerError, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
body, ok := bodyRaw.([]byte)
|
||||||
|
if !ok {
|
||||||
|
respondError(w, http.StatusInternalServerError, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the response
|
||||||
|
w.Header().Set("Content-Type", contentType)
|
||||||
|
w.WriteHeader(status)
|
||||||
|
w.Write(body)
|
||||||
|
}
|
||||||
|
|
||||||
type LogicalResponse struct {
|
type LogicalResponse struct {
|
||||||
LeaseID string `json:"lease_id"`
|
LeaseID string `json:"lease_id"`
|
||||||
Renewable bool `json:"renewable"`
|
Renewable bool `json:"renewable"`
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -182,3 +184,34 @@ func TestLogical_CreateToken(t *testing.T) {
|
||||||
t.Fatalf("should not get cookies: %#v", cookies)
|
t.Fatalf("should not get cookies: %#v", cookies)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLogical_RawHTTP(t *testing.T) {
|
||||||
|
core, _, token := vault.TestCoreUnsealed(t)
|
||||||
|
ln, addr := TestServer(t, core)
|
||||||
|
defer ln.Close()
|
||||||
|
TestServerAuth(t, addr, token)
|
||||||
|
|
||||||
|
resp := testHttpPost(t, addr+"/v1/sys/mounts/foo", map[string]interface{}{
|
||||||
|
"type": "http",
|
||||||
|
})
|
||||||
|
testResponseStatus(t, resp, 204)
|
||||||
|
|
||||||
|
// Get the raw response
|
||||||
|
resp, err := http.Get(addr + "/v1/foo/raw")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
testResponseStatus(t, resp, 200)
|
||||||
|
|
||||||
|
// Test the headers
|
||||||
|
if resp.Header.Get("Content-Type") != "plain/text" {
|
||||||
|
t.Fatalf("Bad: %#v", resp.Header)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the body
|
||||||
|
body := new(bytes.Buffer)
|
||||||
|
io.Copy(body, resp.Body)
|
||||||
|
if string(body.Bytes()) != "hello world" {
|
||||||
|
t.Fatalf("Bad: %s", body.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,24 @@
|
||||||
package logical
|
package logical
|
||||||
|
|
||||||
|
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 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"
|
||||||
|
)
|
||||||
|
|
||||||
// Response is a struct that stores the response of a request.
|
// Response is a struct that stores the response of a request.
|
||||||
// It is used to abstract the details of the higher level request protocol.
|
// It is used to abstract the details of the higher level request protocol.
|
||||||
type Response struct {
|
type Response struct {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package vault
|
package vault
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/vault/audit"
|
"github.com/hashicorp/vault/audit"
|
||||||
|
@ -23,6 +24,9 @@ func TestCore(t *testing.T) *Core {
|
||||||
noopBackends["noop"] = func(map[string]string) (logical.Backend, error) {
|
noopBackends["noop"] = func(map[string]string) (logical.Backend, error) {
|
||||||
return new(framework.Backend), nil
|
return new(framework.Backend), nil
|
||||||
}
|
}
|
||||||
|
noopBackends["http"] = func(map[string]string) (logical.Backend, error) {
|
||||||
|
return new(rawHTTP), nil
|
||||||
|
}
|
||||||
|
|
||||||
physicalBackend := physical.NewInmem()
|
physicalBackend := physical.NewInmem()
|
||||||
c, err := NewCore(&CoreConfig{
|
c, err := NewCore(&CoreConfig{
|
||||||
|
@ -89,3 +93,21 @@ func (n *noopAudit) LogRequest(a *logical.Auth, r *logical.Request) error {
|
||||||
func (n *noopAudit) LogResponse(a *logical.Auth, r *logical.Request, re *logical.Response, err error) error {
|
func (n *noopAudit) LogResponse(a *logical.Auth, r *logical.Request, re *logical.Response, err error) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type rawHTTP struct{}
|
||||||
|
|
||||||
|
func (n *rawHTTP) HandleRequest(req *logical.Request) (*logical.Response, error) {
|
||||||
|
return &logical.Response{
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
logical.HTTPStatusCode: 200,
|
||||||
|
logical.HTTPContentType: "plain/text",
|
||||||
|
logical.HTTPRawBody: []byte("hello world"),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *rawHTTP) SpecialPaths() *logical.Paths {
|
||||||
|
return &logical.Paths{Unauthenticated: []string{"*"}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *rawHTTP) SetLogger(l *log.Logger) {}
|
||||||
|
|
Loading…
Reference in a new issue