You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
boundary/internal/servers/controller/handlers/authtoken_intercept_test.go

195 lines
5.4 KiB

package handlers
import (
"context"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/watchtower/internal/authtoken"
"github.com/hashicorp/watchtower/internal/db"
"github.com/hashicorp/watchtower/internal/gen/controller/api/services"
pbs "github.com/hashicorp/watchtower/internal/gen/controller/api/services"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// Any generated service would do, but using organization since the path is the shortest for testing.
type fakeHandler struct {
pbs.UnimplementedOrganizationServiceServer
validateFn func(context.Context)
}
func (s *fakeHandler) GetOrganization(ctx context.Context, _ *pbs.GetOrganizationRequest) (*pbs.GetOrganizationResponse, error) {
s.validateFn(ctx)
return nil, errors.New("Doesn't matter this is just for testing input.")
}
func TestAuthTokenPublicIdTokenValue(t *testing.T) {
cases := []struct {
name string
in TokenMetadata
wantId string
wantToken string
}{
{
name: "no delimeter",
in: TokenMetadata{
recievedTokenType: authTokenTypeBearer,
bearerPayload: "prefix_publicid_token",
jsCookiePayload: "this_is_just_junk",
httpCookiePayload: "this_can_be_ignored",
},
wantId: "prefix_publicid",
wantToken: "token",
},
{
name: "no delimeter",
in: TokenMetadata{
recievedTokenType: authTokenTypeSplitCookie,
bearerPayload: "this_is_just_junk_that_should_be_ignored",
jsCookiePayload: "prefix_publicid_token",
httpCookiePayload: "cookiepayload",
},
wantId: "prefix_publicid",
wantToken: "tokencookiepayload",
},
{
name: "no delimeter",
in: TokenMetadata{
recievedTokenType: authTokenTypeBearer,
bearerPayload: "this-doesnt-have-the-expected-delimiter",
},
wantId: "",
wantToken: "",
},
{
name: "to many delimeters",
in: TokenMetadata{
recievedTokenType: authTokenTypeBearer,
bearerPayload: "this_has_to_many_delimiters",
},
wantId: "",
wantToken: "",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.wantId, tc.in.publicId(), "got wrong public id")
assert.Equal(t, tc.wantToken, tc.in.token(), "got wrong token value")
})
}
}
func TestAuthTokenAuthenticator(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := authtoken.NewRepository(rw, rw, wrapper)
require.NoError(t, err)
repoFn := func() (*authtoken.Repository, error) {
return repo, nil
}
at := authtoken.TestAuthToken(t, conn, wrapper)
tokValue := at.GetPublicId() + "_" + at.GetToken()
jsCookieVal, httpCookieVal := tokValue[:len(tokValue)/2], tokValue[len(tokValue)/2:]
cases := []struct {
name string
headers map[string]string
cookies []http.Cookie
wantAuthTokMd TokenMetadata
}{
{
name: "Empty headers",
headers: map[string]string{},
wantAuthTokMd: TokenMetadata{recievedTokenType: authTokenTypeUnknown},
},
{
name: "Bear token",
headers: map[string]string{"Authorization": fmt.Sprintf("Bearer %s", tokValue)},
wantAuthTokMd: TokenMetadata{
recievedTokenType: authTokenTypeBearer,
bearerPayload: tokValue,
UserId: at.GetIamUserId(),
},
},
{
name: "Split cookie token",
cookies: []http.Cookie{
{Name: httpOnlyCookieName, Value: httpCookieVal},
{Name: jsVisibleCookieName, Value: jsCookieVal},
},
wantAuthTokMd: TokenMetadata{
recievedTokenType: authTokenTypeSplitCookie,
httpCookiePayload: httpCookieVal,
jsCookiePayload: jsCookieVal,
UserId: at.GetIamUserId(),
},
},
{
name: "Split cookie token only http cookie",
cookies: []http.Cookie{
{Name: httpOnlyCookieName, Value: httpCookieVal},
},
wantAuthTokMd: TokenMetadata{
recievedTokenType: authTokenTypeUnknown,
httpCookiePayload: httpCookieVal,
},
},
{
name: "Split cookie token only js cookie",
cookies: []http.Cookie{
{Name: jsVisibleCookieName, Value: jsCookieVal},
},
wantAuthTokMd: TokenMetadata{
recievedTokenType: authTokenTypeUnknown,
jsCookiePayload: jsCookieVal,
},
},
{
name: "Cookie and auth header",
headers: map[string]string{"Authorization": fmt.Sprintf("Bearer %s", tokValue)},
cookies: []http.Cookie{
{Name: httpOnlyCookieName, Value: httpCookieVal},
{Name: jsVisibleCookieName, Value: jsCookieVal},
},
// We prioritize the auth header over the cookie and if the header is set we ignore the cookies completely.
wantAuthTokMd: TokenMetadata{
recievedTokenType: authTokenTypeBearer,
bearerPayload: tokValue,
UserId: at.GetIamUserId(),
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
hook := &fakeHandler{validateFn: func(ctx context.Context) {
tMD := ToTokenMetadata(ctx)
assert.Equal(t, tc.wantAuthTokMd, tMD)
}}
mux := runtime.NewServeMux(runtime.WithMetadata(TokenAuthenticator(hclog.L(), repoFn)))
require.NoError(t, services.RegisterOrganizationServiceHandlerServer(context.Background(), mux, hook))
req := httptest.NewRequest("GET", "http://127.0.0.1/v1/orgs/1", nil)
for k, v := range tc.headers {
req.Header.Set(k, v)
}
for _, c := range tc.cookies {
req.AddCookie(&c)
}
resp := httptest.NewRecorder()
mux.ServeHTTP(resp, req)
})
}
}