Improve the parsing of headers in test-webhook

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
This commit is contained in:
Ryan Richard 2020-09-10 15:00:53 -07:00 committed by Andrew Keesler
parent 56be4a6761
commit 9baea83066
2 changed files with 161 additions and 168 deletions

View File

@ -18,6 +18,7 @@ import (
"encoding/csv" "encoding/csv"
"encoding/json" "encoding/json"
"fmt" "fmt"
"mime"
"net" "net"
"net/http" "net/http"
"os" "os"
@ -122,13 +123,15 @@ func (w *webhook) ServeHTTP(rsp http.ResponseWriter, req *http.Request) {
return return
} }
if !contains(req.Header.Values("Content-Type"), "application/json") { if !headerContains(req, "Content-Type", "application/json") {
klog.InfoS("wrong content type", "Content-Type", req.Header.Values("Content-Type")) klog.InfoS("content type is not application/json", "Content-Type", req.Header.Values("Content-Type"))
rsp.WriteHeader(http.StatusUnsupportedMediaType) rsp.WriteHeader(http.StatusUnsupportedMediaType)
return return
} }
if !contains(req.Header.Values("Accept"), "application/json") { if !headerContains(req, "Accept", "application/json") &&
klog.InfoS("wrong accept type", "Accept", req.Header.Values("Accept")) !headerContains(req, "Accept", "application/*") &&
!headerContains(req, "Accept", "*/*") {
klog.InfoS("client does not accept application/json", "Accept", req.Header.Values("Accept"))
rsp.WriteHeader(http.StatusUnsupportedMediaType) rsp.WriteHeader(http.StatusUnsupportedMediaType)
return return
} }
@ -190,12 +193,17 @@ func (w *webhook) ServeHTTP(rsp http.ResponseWriter, req *http.Request) {
respondWithAuthenticated(rsp, secret.ObjectMeta.Name, string(secret.UID), groups) respondWithAuthenticated(rsp, secret.ObjectMeta.Name, string(secret.UID), groups)
} }
func contains(ss []string, s string) bool { func headerContains(req *http.Request, headerName, s string) bool {
for i := range ss { headerValues := req.Header.Values(headerName)
if ss[i] == s { for i := range headerValues {
mimeTypes := strings.Split(headerValues[i], ",")
for _, mimeType := range mimeTypes {
mediaType, _, _ := mime.ParseMediaType(mimeType)
if mediaType == s {
return true return true
} }
} }
}
return false return false
} }

View File

@ -101,8 +101,8 @@ func TestWebhook(t *testing.T) {
goodURL := fmt.Sprintf("https://%s/authenticate", l.Addr().String()) goodURL := fmt.Sprintf("https://%s/authenticate", l.Addr().String())
goodRequestHeaders := map[string][]string{ goodRequestHeaders := map[string][]string{
"Content-Type": {"application/json"}, "Content-Type": {"application/json; charset=UTF-8"},
"Accept": {"application/json"}, "Accept": {"application/json, */*"},
} }
tests := []struct { tests := []struct {
@ -121,13 +121,9 @@ func TestWebhook(t *testing.T) {
url: goodURL, url: goodURL,
method: http.MethodPost, method: http.MethodPost,
headers: goodRequestHeaders, headers: goodRequestHeaders,
body: func() (io.ReadCloser, error) { body: func() (io.ReadCloser, error) { return newTokenReviewBody(user + ":" + password) },
return newTokenReviewBody(user + ":" + password)
},
wantStatus: http.StatusOK, wantStatus: http.StatusOK,
wantHeaders: map[string][]string{ wantHeaders: map[string][]string{"Content-Type": {"application/json"}},
"Content-Type": {"application/json"},
},
wantBody: authenticatedResponseJSON(user, uid, []string{group0, group1}), wantBody: authenticatedResponseJSON(user, uid, []string{group0, group1}),
}, },
{ {
@ -135,13 +131,9 @@ func TestWebhook(t *testing.T) {
url: goodURL, url: goodURL,
method: http.MethodPost, method: http.MethodPost,
headers: goodRequestHeaders, headers: goodRequestHeaders,
body: func() (io.ReadCloser, error) { body: func() (io.ReadCloser, error) { return newTokenReviewBody(oneGroupUser + ":" + oneGroupPassword) },
return newTokenReviewBody(oneGroupUser + ":" + oneGroupPassword)
},
wantStatus: http.StatusOK, wantStatus: http.StatusOK,
wantHeaders: map[string][]string{ wantHeaders: map[string][]string{"Content-Type": {"application/json"}},
"Content-Type": {"application/json"},
},
wantBody: authenticatedResponseJSON(oneGroupUser, oneGroupUID, []string{group0}), wantBody: authenticatedResponseJSON(oneGroupUser, oneGroupUID, []string{group0}),
}, },
{ {
@ -149,13 +141,9 @@ func TestWebhook(t *testing.T) {
url: goodURL, url: goodURL,
method: http.MethodPost, method: http.MethodPost,
headers: goodRequestHeaders, headers: goodRequestHeaders,
body: func() (io.ReadCloser, error) { body: func() (io.ReadCloser, error) { return newTokenReviewBody(noGroupUser + ":" + noGroupPassword) },
return newTokenReviewBody(noGroupUser + ":" + noGroupPassword)
},
wantStatus: http.StatusOK, wantStatus: http.StatusOK,
wantHeaders: map[string][]string{ wantHeaders: map[string][]string{"Content-Type": {"application/json"}},
"Content-Type": {"application/json"},
},
wantBody: authenticatedResponseJSON(noGroupUser, noGroupUID, []string{}), wantBody: authenticatedResponseJSON(noGroupUser, noGroupUID, []string{}),
}, },
{ {
@ -163,13 +151,9 @@ func TestWebhook(t *testing.T) {
url: goodURL, url: goodURL,
method: http.MethodPost, method: http.MethodPost,
headers: goodRequestHeaders, headers: goodRequestHeaders,
body: func() (io.ReadCloser, error) { body: func() (io.ReadCloser, error) { return newTokenReviewBody(otherUser + ":" + password) },
return newTokenReviewBody(otherUser + ":" + password)
},
wantStatus: http.StatusOK, wantStatus: http.StatusOK,
wantHeaders: map[string][]string{ wantHeaders: map[string][]string{"Content-Type": {"application/json"}},
"Content-Type": {"application/json"},
},
wantBody: unauthenticatedResponseJSON(), wantBody: unauthenticatedResponseJSON(),
}, },
{ {
@ -177,13 +161,9 @@ func TestWebhook(t *testing.T) {
url: goodURL, url: goodURL,
method: http.MethodPost, method: http.MethodPost,
headers: goodRequestHeaders, headers: goodRequestHeaders,
body: func() (io.ReadCloser, error) { body: func() (io.ReadCloser, error) { return newTokenReviewBody(passwordUndefinedUser + ":foo") },
return newTokenReviewBody(passwordUndefinedUser + ":foo")
},
wantStatus: http.StatusOK, wantStatus: http.StatusOK,
wantHeaders: map[string][]string{ wantHeaders: map[string][]string{"Content-Type": {"application/json"}},
"Content-Type": {"application/json"},
},
wantBody: unauthenticatedResponseJSON(), wantBody: unauthenticatedResponseJSON(),
}, },
{ {
@ -195,9 +175,7 @@ func TestWebhook(t *testing.T) {
return newTokenReviewBody(underfinedGroupsUser + ":" + undefinedGroupsPassword) return newTokenReviewBody(underfinedGroupsUser + ":" + undefinedGroupsPassword)
}, },
wantStatus: http.StatusOK, wantStatus: http.StatusOK,
wantHeaders: map[string][]string{ wantHeaders: map[string][]string{"Content-Type": {"application/json"}},
"Content-Type": {"application/json"},
},
wantBody: authenticatedResponseJSON(underfinedGroupsUser, underfinedGroupsUID, []string{}), wantBody: authenticatedResponseJSON(underfinedGroupsUser, underfinedGroupsUID, []string{}),
}, },
{ {
@ -205,13 +183,9 @@ func TestWebhook(t *testing.T) {
url: goodURL, url: goodURL,
method: http.MethodPost, method: http.MethodPost,
headers: goodRequestHeaders, headers: goodRequestHeaders,
body: func() (io.ReadCloser, error) { body: func() (io.ReadCloser, error) { return newTokenReviewBody(passwordUndefinedUser + ":foo") },
return newTokenReviewBody(passwordUndefinedUser + ":foo")
},
wantStatus: http.StatusOK, wantStatus: http.StatusOK,
wantHeaders: map[string][]string{ wantHeaders: map[string][]string{"Content-Type": {"application/json"}},
"Content-Type": {"application/json"},
},
wantBody: unauthenticatedResponseJSON(), wantBody: unauthenticatedResponseJSON(),
}, },
{ {
@ -219,13 +193,9 @@ func TestWebhook(t *testing.T) {
url: goodURL, url: goodURL,
method: http.MethodPost, method: http.MethodPost,
headers: goodRequestHeaders, headers: goodRequestHeaders,
body: func() (io.ReadCloser, error) { body: func() (io.ReadCloser, error) { return newTokenReviewBody(user + ":" + otherPassword) },
return newTokenReviewBody(user + ":" + otherPassword)
},
wantStatus: http.StatusOK, wantStatus: http.StatusOK,
wantHeaders: map[string][]string{ wantHeaders: map[string][]string{"Content-Type": {"application/json"}},
"Content-Type": {"application/json"},
},
wantBody: unauthenticatedResponseJSON(), wantBody: unauthenticatedResponseJSON(),
}, },
{ {
@ -233,13 +203,9 @@ func TestWebhook(t *testing.T) {
url: goodURL, url: goodURL,
method: http.MethodPost, method: http.MethodPost,
headers: goodRequestHeaders, headers: goodRequestHeaders,
body: func() (io.ReadCloser, error) { body: func() (io.ReadCloser, error) { return newTokenReviewBody(user + ":" + "some-non-existent-password") },
return newTokenReviewBody(user + ":" + "some-non-existent-password")
},
wantStatus: http.StatusOK, wantStatus: http.StatusOK,
wantHeaders: map[string][]string{ wantHeaders: map[string][]string{"Content-Type": {"application/json"}},
"Content-Type": {"application/json"},
},
wantBody: unauthenticatedResponseJSON(), wantBody: unauthenticatedResponseJSON(),
}, },
{ {
@ -247,13 +213,9 @@ func TestWebhook(t *testing.T) {
url: goodURL, url: goodURL,
method: http.MethodPost, method: http.MethodPost,
headers: goodRequestHeaders, headers: goodRequestHeaders,
body: func() (io.ReadCloser, error) { body: func() (io.ReadCloser, error) { return newTokenReviewBody("some-non-existent-user" + ":" + password) },
return newTokenReviewBody("some-non-existent-user" + ":" + password)
},
wantStatus: http.StatusOK, wantStatus: http.StatusOK,
wantHeaders: map[string][]string{ wantHeaders: map[string][]string{"Content-Type": {"application/json"}},
"Content-Type": {"application/json"},
},
wantBody: unauthenticatedResponseJSON(), wantBody: unauthenticatedResponseJSON(),
}, },
{ {
@ -261,9 +223,7 @@ func TestWebhook(t *testing.T) {
url: goodURL, url: goodURL,
method: http.MethodPost, method: http.MethodPost,
headers: goodRequestHeaders, headers: goodRequestHeaders,
body: func() (io.ReadCloser, error) { body: func() (io.ReadCloser, error) { return newTokenReviewBody(user) },
return newTokenReviewBody(user)
},
wantStatus: http.StatusBadRequest, wantStatus: http.StatusBadRequest,
}, },
{ {
@ -271,13 +231,9 @@ func TestWebhook(t *testing.T) {
url: goodURL, url: goodURL,
method: http.MethodPost, method: http.MethodPost,
headers: goodRequestHeaders, headers: goodRequestHeaders,
body: func() (io.ReadCloser, error) { body: func() (io.ReadCloser, error) { return newTokenReviewBody(colonUser + ":" + colonPassword) },
return newTokenReviewBody(colonUser + ":" + colonPassword)
},
wantStatus: http.StatusOK, wantStatus: http.StatusOK,
wantHeaders: map[string][]string{ wantHeaders: map[string][]string{"Content-Type": {"application/json"}},
"Content-Type": {"application/json"},
},
wantBody: authenticatedResponseJSON(colonUser, colonUID, []string{group0, group1}), wantBody: authenticatedResponseJSON(colonUser, colonUID, []string{group0, group1}),
}, },
{ {
@ -285,9 +241,7 @@ func TestWebhook(t *testing.T) {
url: fmt.Sprintf("https://%s/tuna", l.Addr().String()), url: fmt.Sprintf("https://%s/tuna", l.Addr().String()),
method: http.MethodPost, method: http.MethodPost,
headers: goodRequestHeaders, headers: goodRequestHeaders,
body: func() (io.ReadCloser, error) { body: func() (io.ReadCloser, error) { return newTokenReviewBody("some-token") },
return newTokenReviewBody("some-token")
},
wantStatus: http.StatusNotFound, wantStatus: http.StatusNotFound,
}, },
{ {
@ -295,9 +249,7 @@ func TestWebhook(t *testing.T) {
url: goodURL, url: goodURL,
method: http.MethodGet, method: http.MethodGet,
headers: goodRequestHeaders, headers: goodRequestHeaders,
body: func() (io.ReadCloser, error) { body: func() (io.ReadCloser, error) { return newTokenReviewBody("some-token") },
return newTokenReviewBody("some-token")
},
wantStatus: http.StatusMethodNotAllowed, wantStatus: http.StatusMethodNotAllowed,
}, },
{ {
@ -308,9 +260,7 @@ func TestWebhook(t *testing.T) {
"Content-Type": {"application/xml"}, "Content-Type": {"application/xml"},
"Accept": {"application/json"}, "Accept": {"application/json"},
}, },
body: func() (io.ReadCloser, error) { body: func() (io.ReadCloser, error) { return newTokenReviewBody("some-token") },
return newTokenReviewBody("some-token")
},
wantStatus: http.StatusUnsupportedMediaType, wantStatus: http.StatusUnsupportedMediaType,
}, },
{ {
@ -321,19 +271,54 @@ func TestWebhook(t *testing.T) {
"Content-Type": {"application/json"}, "Content-Type": {"application/json"},
"Accept": {"application/xml"}, "Accept": {"application/xml"},
}, },
body: func() (io.ReadCloser, error) { body: func() (io.ReadCloser, error) { return newTokenReviewBody("some-token") },
return newTokenReviewBody("some-token")
},
wantStatus: http.StatusUnsupportedMediaType, wantStatus: http.StatusUnsupportedMediaType,
}, },
{
name: "success when there are multiple accepts and one of them is json",
url: goodURL,
method: http.MethodPost,
headers: map[string][]string{
"Content-Type": {"application/json"},
"Accept": {"something/else, application/xml, application/json"},
},
body: func() (io.ReadCloser, error) { return newTokenReviewBody(user + ":" + password) },
wantStatus: http.StatusOK,
wantHeaders: map[string][]string{"Content-Type": {"application/json"}},
wantBody: authenticatedResponseJSON(user, uid, []string{group0, group1}),
},
{
name: "success when there are multiple accepts and one of them is */*",
url: goodURL,
method: http.MethodPost,
headers: map[string][]string{
"Content-Type": {"application/json"},
"Accept": {"something/else, */*, application/foo"},
},
body: func() (io.ReadCloser, error) { return newTokenReviewBody(user + ":" + password) },
wantStatus: http.StatusOK,
wantHeaders: map[string][]string{"Content-Type": {"application/json"}},
wantBody: authenticatedResponseJSON(user, uid, []string{group0, group1}),
},
{
name: "success when there are multiple accepts and one of them is application/*",
url: goodURL,
method: http.MethodPost,
headers: map[string][]string{
"Content-Type": {"application/json"},
"Accept": {"something/else, application/*, application/foo"},
},
body: func() (io.ReadCloser, error) { return newTokenReviewBody(user + ":" + password) },
wantStatus: http.StatusOK,
wantHeaders: map[string][]string{"Content-Type": {"application/json"}},
wantBody: authenticatedResponseJSON(user, uid, []string{group0, group1}),
},
{ {
name: "bad body", name: "bad body",
url: goodURL, url: goodURL,
method: http.MethodPost, method: http.MethodPost,
headers: goodRequestHeaders, headers: goodRequestHeaders,
body: func() (io.ReadCloser, error) { body: func() (io.ReadCloser, error) { return ioutil.NopCloser(bytes.NewBuffer([]byte("invalid body"))), nil },
return ioutil.NopCloser(bytes.NewBuffer([]byte("invalid body"))), nil
},
wantStatus: http.StatusBadRequest, wantStatus: http.StatusBadRequest,
}, },
} }