Improve the parsing of headers in test-webhook
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
This commit is contained in:
parent
56be4a6761
commit
9baea83066
@ -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,10 +193,15 @@ 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 {
|
||||||
return true
|
mimeTypes := strings.Split(headerValues[i], ",")
|
||||||
|
for _, mimeType := range mimeTypes {
|
||||||
|
mediaType, _, _ := mime.ParseMediaType(mimeType)
|
||||||
|
if mediaType == s {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -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 {
|
||||||
@ -117,74 +117,54 @@ func TestWebhook(t *testing.T) {
|
|||||||
wantBody *string
|
wantBody *string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "success for a user who belongs to multiple groups",
|
name: "success for a user who belongs to multiple groups",
|
||||||
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,
|
||||||
},
|
wantHeaders: map[string][]string{"Content-Type": {"application/json"}},
|
||||||
wantStatus: http.StatusOK,
|
wantBody: authenticatedResponseJSON(user, uid, []string{group0, group1}),
|
||||||
wantHeaders: map[string][]string{
|
|
||||||
"Content-Type": {"application/json"},
|
|
||||||
},
|
|
||||||
wantBody: authenticatedResponseJSON(user, uid, []string{group0, group1}),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "success for a user who belongs to one groups",
|
name: "success for a user who belongs to one groups",
|
||||||
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,
|
||||||
},
|
wantHeaders: map[string][]string{"Content-Type": {"application/json"}},
|
||||||
wantStatus: http.StatusOK,
|
wantBody: authenticatedResponseJSON(oneGroupUser, oneGroupUID, []string{group0}),
|
||||||
wantHeaders: map[string][]string{
|
|
||||||
"Content-Type": {"application/json"},
|
|
||||||
},
|
|
||||||
wantBody: authenticatedResponseJSON(oneGroupUser, oneGroupUID, []string{group0}),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "success for a user who belongs to no groups",
|
name: "success for a user who belongs to no groups",
|
||||||
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,
|
||||||
},
|
wantHeaders: map[string][]string{"Content-Type": {"application/json"}},
|
||||||
wantStatus: http.StatusOK,
|
wantBody: authenticatedResponseJSON(noGroupUser, noGroupUID, []string{}),
|
||||||
wantHeaders: map[string][]string{
|
|
||||||
"Content-Type": {"application/json"},
|
|
||||||
},
|
|
||||||
wantBody: authenticatedResponseJSON(noGroupUser, noGroupUID, []string{}),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "wrong username for password",
|
name: "wrong username for password",
|
||||||
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,
|
||||||
},
|
wantHeaders: map[string][]string{"Content-Type": {"application/json"}},
|
||||||
wantStatus: http.StatusOK,
|
wantBody: unauthenticatedResponseJSON(),
|
||||||
wantHeaders: map[string][]string{
|
|
||||||
"Content-Type": {"application/json"},
|
|
||||||
},
|
|
||||||
wantBody: unauthenticatedResponseJSON(),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "when a user has no password hash in the secret",
|
name: "when a user has no password hash in the secret",
|
||||||
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,
|
||||||
},
|
wantHeaders: map[string][]string{"Content-Type": {"application/json"}},
|
||||||
wantStatus: http.StatusOK,
|
wantBody: unauthenticatedResponseJSON(),
|
||||||
wantHeaders: map[string][]string{
|
|
||||||
"Content-Type": {"application/json"},
|
|
||||||
},
|
|
||||||
wantBody: unauthenticatedResponseJSON(),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "success for a user has no groups defined in the secret",
|
name: "success for a user has no groups defined in the secret",
|
||||||
@ -194,110 +174,82 @@ func TestWebhook(t *testing.T) {
|
|||||||
body: func() (io.ReadCloser, error) {
|
body: func() (io.ReadCloser, error) {
|
||||||
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{}),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "when a user has empty string as their password",
|
name: "when a user has empty string as their password",
|
||||||
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,
|
||||||
},
|
wantHeaders: map[string][]string{"Content-Type": {"application/json"}},
|
||||||
wantStatus: http.StatusOK,
|
wantBody: unauthenticatedResponseJSON(),
|
||||||
wantHeaders: map[string][]string{
|
|
||||||
"Content-Type": {"application/json"},
|
|
||||||
},
|
|
||||||
wantBody: unauthenticatedResponseJSON(),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "wrong password for username",
|
name: "wrong password for username",
|
||||||
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,
|
||||||
},
|
wantHeaders: map[string][]string{"Content-Type": {"application/json"}},
|
||||||
wantStatus: http.StatusOK,
|
wantBody: unauthenticatedResponseJSON(),
|
||||||
wantHeaders: map[string][]string{
|
|
||||||
"Content-Type": {"application/json"},
|
|
||||||
},
|
|
||||||
wantBody: unauthenticatedResponseJSON(),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "non-existent password for username",
|
name: "non-existent password for username",
|
||||||
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,
|
||||||
},
|
wantHeaders: map[string][]string{"Content-Type": {"application/json"}},
|
||||||
wantStatus: http.StatusOK,
|
wantBody: unauthenticatedResponseJSON(),
|
||||||
wantHeaders: map[string][]string{
|
|
||||||
"Content-Type": {"application/json"},
|
|
||||||
},
|
|
||||||
wantBody: unauthenticatedResponseJSON(),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "non-existent username",
|
name: "non-existent username",
|
||||||
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,
|
||||||
},
|
wantHeaders: map[string][]string{"Content-Type": {"application/json"}},
|
||||||
wantStatus: http.StatusOK,
|
wantBody: unauthenticatedResponseJSON(),
|
||||||
wantHeaders: map[string][]string{
|
|
||||||
"Content-Type": {"application/json"},
|
|
||||||
},
|
|
||||||
wantBody: unauthenticatedResponseJSON(),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "bad token format (missing colon)",
|
name: "bad token format (missing colon)",
|
||||||
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,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "password contains colon",
|
name: "password contains colon",
|
||||||
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,
|
||||||
},
|
wantHeaders: map[string][]string{"Content-Type": {"application/json"}},
|
||||||
wantStatus: http.StatusOK,
|
wantBody: authenticatedResponseJSON(colonUser, colonUID, []string{group0, group1}),
|
||||||
wantHeaders: map[string][]string{
|
|
||||||
"Content-Type": {"application/json"},
|
|
||||||
},
|
|
||||||
wantBody: authenticatedResponseJSON(colonUser, colonUID, []string{group0, group1}),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "bad path",
|
name: "bad path",
|
||||||
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,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "bad method",
|
name: "bad method",
|
||||||
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: "bad body",
|
name: "success when there are multiple accepts and one of them is json",
|
||||||
url: goodURL,
|
url: goodURL,
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
headers: goodRequestHeaders,
|
headers: map[string][]string{
|
||||||
body: func() (io.ReadCloser, error) {
|
"Content-Type": {"application/json"},
|
||||||
return ioutil.NopCloser(bytes.NewBuffer([]byte("invalid body"))), nil
|
"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",
|
||||||
|
url: goodURL,
|
||||||
|
method: http.MethodPost,
|
||||||
|
headers: goodRequestHeaders,
|
||||||
|
body: func() (io.ReadCloser, error) { return ioutil.NopCloser(bytes.NewBuffer([]byte("invalid body"))), nil },
|
||||||
wantStatus: http.StatusBadRequest,
|
wantStatus: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user