blob: 43a0013ad794aa09b49694368729e51e738d5a0b [file] [log] [blame]
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package access
import (
"context"
"errors"
"fmt"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"google.golang.org/api/idtoken"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
func TestIAPFromContextError(t *testing.T) {
ctx := context.WithValue(context.Background(), contextIAP, "dance party")
if got, err := IAPFromContext(ctx); got != nil || err == nil {
t.Errorf("IAPFromContext(ctx) = %v, %s; want error", got, err)
}
}
func TestIAPAuthFunc(t *testing.T) {
want := &IAPFields{
Email: "charlie@brown.com",
ID: "chaz.service.moo",
}
wantJWTToken := "eyJhb.eyJzdDIyfQ.Bh17Fl2gFjyLh6mo1GjqSPnGUg8MRLAE1Vdo3Z3gvdI"
wantAudience := "foo/bar/zar"
ctx := metadata.NewIncomingContext(context.Background(), metadata.New(map[string]string{
iapHeaderJWT: wantJWTToken,
iapHeaderEmail: want.Email,
iapHeaderID: want.ID,
}))
testValidator := func(ctx context.Context, token, audience string) (*idtoken.Payload, error) {
if token != wantJWTToken || audience != wantAudience {
return nil, fmt.Errorf("testValidator(%q, %q); want %q, %q", token, audience, wantJWTToken, wantAudience)
}
return &idtoken.Payload{
Issuer: "https://cloud.google.com/iap",
Audience: audience,
Expires: time.Now().Add(time.Minute).Unix(),
IssuedAt: time.Now().Add(-time.Minute).Unix(),
}, nil
}
authFunc := iapAuthFunc(wantAudience, testValidator)
gotCtx, err := authFunc(ctx)
if err != nil {
t.Fatalf("authFunc(ctx) = %+v, %s; want ctx, no error", gotCtx, err)
}
got, err := IAPFromContext(gotCtx)
if err != nil {
t.Fatalf("IAPFromContext(ctx) = %+v, %s; want no error", got, err)
}
if diff := cmp.Diff(got, want); diff != "" {
t.Errorf("ctx.Value(%v) mismatch (-got, +want):\n%s", contextIAP, diff)
}
}
func TestIAPAuthFuncError(t *testing.T) {
testCases := []struct {
desc string
validator validator
ctx context.Context
audience string
wantErr codes.Code
}{
{
desc: "invalid context",
validator: func(ctx context.Context, token, audience string) (*idtoken.Payload, error) {
return &idtoken.Payload{
Issuer: "https://cloud.google.com/iap",
Audience: audience,
Expires: time.Now().Add(time.Minute).Unix(),
IssuedAt: time.Now().Add(-time.Minute).Unix(),
}, nil
},
ctx: context.Background(),
audience: "foo/bar/zar",
wantErr: codes.Internal,
},
{
desc: "invalid jwt header",
validator: func(ctx context.Context, token, audience string) (*idtoken.Payload, error) {
return &idtoken.Payload{
Issuer: "https://cloud.google.com/iap",
Audience: audience,
Expires: time.Now().Add(time.Minute).Unix(),
IssuedAt: time.Now().Add(-time.Minute).Unix(),
}, nil
},
ctx: metadata.NewIncomingContext(context.Background(), metadata.New(map[string]string{
iapHeaderEmail: "mary@foo.com",
iapHeaderID: "chaz.service.moo",
})),
audience: "foo/bar/zar",
wantErr: codes.Unauthenticated,
},
{
desc: "failed validation",
validator: func(ctx context.Context, token, audience string) (*idtoken.Payload, error) {
return nil, errors.New("validation failed")
},
ctx: metadata.NewIncomingContext(context.Background(), metadata.New(map[string]string{
iapHeaderJWT: "xyz",
iapHeaderEmail: "mary@foo.com",
iapHeaderID: "chaz.service.moo",
})),
audience: "foo/bar/zar",
wantErr: codes.Unauthenticated,
},
{
desc: "wrong issuer",
validator: func(ctx context.Context, token, audience string) (*idtoken.Payload, error) {
return &idtoken.Payload{
Issuer: "https://cloud.google.com/iap-wrong",
Audience: audience,
Expires: time.Now().Add(time.Minute).Unix(),
IssuedAt: time.Now().Add(-time.Minute).Unix(),
}, nil
},
ctx: metadata.NewIncomingContext(context.Background(), metadata.New(map[string]string{
iapHeaderJWT: "xyz",
iapHeaderEmail: "mary@foo.com",
iapHeaderID: "chaz.service.moo",
})),
audience: "foo/bar/zar",
wantErr: codes.Unauthenticated,
},
{
desc: "jwt expired",
validator: func(ctx context.Context, token, audience string) (*idtoken.Payload, error) {
return &idtoken.Payload{
Issuer: "https://cloud.google.com/iap",
Audience: audience,
Expires: time.Now().Add(-time.Minute).Unix(),
IssuedAt: time.Now().Add(-10 * time.Minute).Unix(),
}, nil
},
ctx: metadata.NewIncomingContext(context.Background(), metadata.New(map[string]string{
iapHeaderJWT: "xyz",
iapHeaderEmail: "mary@foo.com",
iapHeaderID: "chaz.service.moo",
})),
audience: "foo/bar/zar",
wantErr: codes.Unauthenticated,
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
authFunc := iapAuthFunc(tc.audience, tc.validator)
gotCtx, err := authFunc(tc.ctx)
if err == nil {
t.Fatalf("authFunc(ctx) = %s, nil; want error", gotCtx)
}
if status.Code(err) != tc.wantErr {
t.Fatalf("authFunc(ctx) = nil, %s; want %s", status.Code(err), tc.wantErr)
}
})
}
}
func TestContextWithIAPMDError(t *testing.T) {
testCases := []struct {
desc string
md metadata.MD
}{
{
desc: "missing email header",
md: metadata.New(map[string]string{
iapHeaderJWT: "jwt",
iapHeaderID: "id",
}),
},
{
desc: "missing id header",
md: metadata.New(map[string]string{
iapHeaderJWT: "jwt",
iapHeaderEmail: "email",
}),
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
ctx, err := contextWithIAPMD(context.Background(), tc.md)
if err == nil {
t.Errorf("contextWithIAPMD(ctx, %v) = %+v, %s; want ctx, error", tc.md, ctx, err)
}
})
}
}
func TestIAPAudienceGCE(t *testing.T) {
want := "/projects/11/global/backendServices/bar"
if got := IAPAudienceGCE(11, "bar"); got != want {
t.Errorf("IAPAudienceGCE(11, bar) = %s; want %s", got, want)
}
}
func TestIAPAudience(t *testing.T) {
want := "/projects/11/apps/bar"
if got := IAPAudienceAppEngine(11, "bar"); got != want {
t.Errorf("IAPAudienceAppEngine(11, bar) = %s; want %s", got, want)
}
}