blob: 7e80bfdb57b3857201adfef081fea864e13b9567 [file] [log] [blame]
// Copyright 2017 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package openid
import (
"context"
"strings"
"testing"
"time"
"go.chromium.org/luci/common/clock"
"go.chromium.org/luci/common/clock/testclock"
"go.chromium.org/luci/server/auth/signing/signingtest"
. "github.com/smartystreets/goconvey/convey"
. "go.chromium.org/luci/common/testing/assertions"
)
func TestVerifyIDToken(t *testing.T) {
t.Parallel()
ctx := context.Background()
ctx, tc := testclock.UseTime(ctx, time.Unix(1442540000, 0))
// Prepare the the signing keys.
const issuer = "https://issuer.example.com"
const signingKeyID = "signing-key"
signer := signingtest.NewSigner(nil)
jwks, _ := NewJSONWebKeySet(jwksForTest(signingKeyID, &signer.KeyForTest().PublicKey))
newToken := func() *IDToken {
return &IDToken{
Iss: issuer,
EmailVerified: true,
Sub: "user_id_sub",
Email: "user@example.com",
Name: "Some Dude",
Picture: "https://picture/url/s64/photo.jpg",
Aud: "client_id",
Iat: clock.Now(ctx).Unix(),
Exp: clock.Now(ctx).Add(time.Hour).Unix(),
}
}
signToken := func(t *IDToken) string {
return idTokenForTest(ctx, t, signingKeyID, signer)
}
verifyToken := func(token string) (*IDToken, error) {
return VerifyIDToken(ctx, token, jwks, issuer)
}
Convey("Happy path", t, func() {
original := newToken()
parsed, err := verifyToken(signToken(original))
So(err, ShouldBeNil)
So(parsed, ShouldResemble, original)
// Alternative issuer form.
original.Iss = strings.TrimPrefix(issuer, "https://")
parsed, err = verifyToken(signToken(original))
So(err, ShouldBeNil)
So(parsed, ShouldResemble, original)
})
Convey("Bad JWT", t, func() {
_, err := verifyToken("IMANOTAJWT")
So(err, ShouldErrLike, "bad JWT")
})
Convey("Bad body (not JSON)", t, func() {
_, err := verifyToken(jwtForTest(ctx, []byte("IAMNOTJSON"), signingKeyID, signer))
So(err, ShouldErrLike, "not JSON")
})
Convey("Bad issuer", t, func() {
tok := newToken()
tok.Iss = "something else"
_, err := verifyToken(signToken(tok))
So(err, ShouldErrLike, "expecting issuer")
})
Convey("Unverified email", t, func() {
tok := newToken()
tok.EmailVerified = false
_, err := verifyToken(signToken(tok))
So(err, ShouldErrLike, "is not verified")
})
Convey("No audience", t, func() {
tok := newToken()
tok.Aud = ""
_, err := verifyToken(signToken(tok))
So(err, ShouldErrLike, "the audience is missing")
})
Convey("No subject", t, func() {
tok := newToken()
tok.Sub = ""
_, err := verifyToken(signToken(tok))
So(err, ShouldErrLike, "the subject is missing")
})
Convey("Expired", t, func() {
tok := signToken(newToken())
// Still good enough after 1h due to clock skew protection.
tc.Add(time.Hour)
_, err := verifyToken(tok)
So(err, ShouldBeNil)
// Some moments later is considered expired for real.
tc.Add(allowedClockSkew + time.Second)
_, err = verifyToken(tok)
So(err, ShouldErrLike, "expired")
})
}