Merge pull request #266 from hashicorp/f-http

Support for HTTP Response overwriting
This commit is contained in:
Armon Dadgar 2015-05-27 14:21:09 -07:00
commit 4946a66e30
4 changed files with 132 additions and 0 deletions

View file

@ -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"`

View file

@ -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())
}
}

View file

@ -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 {

View file

@ -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) {}