go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/auth/openid/id_token_test.go (about)

     1  // Copyright 2017 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package openid
    16  
    17  import (
    18  	"context"
    19  	"strings"
    20  	"testing"
    21  	"time"
    22  
    23  	"go.chromium.org/luci/common/clock"
    24  	"go.chromium.org/luci/common/clock/testclock"
    25  
    26  	"go.chromium.org/luci/server/auth/signing/signingtest"
    27  
    28  	. "github.com/smartystreets/goconvey/convey"
    29  	. "go.chromium.org/luci/common/testing/assertions"
    30  )
    31  
    32  func TestVerifyIDToken(t *testing.T) {
    33  	t.Parallel()
    34  
    35  	ctx := context.Background()
    36  	ctx, tc := testclock.UseTime(ctx, time.Unix(1442540000, 0))
    37  
    38  	// Prepare the signing keys.
    39  	const issuer = "https://issuer.example.com"
    40  	const signingKeyID = "signing-key"
    41  
    42  	signer := signingtest.NewSigner(nil)
    43  	jwks, _ := NewJSONWebKeySet(jwksForTest(signingKeyID, &signer.KeyForTest().PublicKey))
    44  
    45  	newToken := func() *IDToken {
    46  		return &IDToken{
    47  			Iss:           issuer,
    48  			EmailVerified: true,
    49  			Sub:           "user_id_sub",
    50  			Email:         "user@example.com",
    51  			Name:          "Some Dude",
    52  			Picture:       "https://picture/url/s64/photo.jpg",
    53  			Aud:           "client_id",
    54  			Iat:           clock.Now(ctx).Unix(),
    55  			Exp:           clock.Now(ctx).Add(time.Hour).Unix(),
    56  		}
    57  	}
    58  	signToken := func(t *IDToken) string {
    59  		return idTokenForTest(ctx, t, signingKeyID, signer)
    60  	}
    61  	verifyToken := func(token string) (*IDToken, error) {
    62  		return VerifyIDToken(ctx, token, jwks, issuer)
    63  	}
    64  
    65  	Convey("Happy path", t, func() {
    66  		original := newToken()
    67  		parsed, err := verifyToken(signToken(original))
    68  		So(err, ShouldBeNil)
    69  		So(parsed, ShouldResemble, original)
    70  
    71  		// Alternative issuer form.
    72  		original.Iss = strings.TrimPrefix(issuer, "https://")
    73  		parsed, err = verifyToken(signToken(original))
    74  		So(err, ShouldBeNil)
    75  		So(parsed, ShouldResemble, original)
    76  	})
    77  
    78  	Convey("Bad JWT", t, func() {
    79  		_, err := verifyToken("IMANOTAJWT")
    80  		So(err, ShouldErrLike, "bad JWT")
    81  	})
    82  
    83  	Convey("Bad body (not JSON)", t, func() {
    84  		_, err := verifyToken(jwtForTest(ctx, []byte("IAMNOTJSON"), signingKeyID, signer))
    85  		So(err, ShouldErrLike, "can't deserialize JSON")
    86  	})
    87  
    88  	Convey("Bad issuer", t, func() {
    89  		tok := newToken()
    90  		tok.Iss = "something else"
    91  		_, err := verifyToken(signToken(tok))
    92  		So(err, ShouldErrLike, "expecting issuer")
    93  	})
    94  
    95  	Convey("Unverified email", t, func() {
    96  		tok := newToken()
    97  		tok.EmailVerified = false
    98  		_, err := verifyToken(signToken(tok))
    99  		So(err, ShouldErrLike, "is not verified")
   100  	})
   101  
   102  	Convey("No audience", t, func() {
   103  		tok := newToken()
   104  		tok.Aud = ""
   105  		_, err := verifyToken(signToken(tok))
   106  		So(err, ShouldErrLike, "the audience is missing")
   107  	})
   108  
   109  	Convey("No subject", t, func() {
   110  		tok := newToken()
   111  		tok.Sub = ""
   112  		_, err := verifyToken(signToken(tok))
   113  		So(err, ShouldErrLike, "the subject is missing")
   114  	})
   115  
   116  	Convey("Expired", t, func() {
   117  		tok := signToken(newToken())
   118  
   119  		// Still good enough after 1h due to clock skew protection.
   120  		tc.Add(time.Hour)
   121  		_, err := verifyToken(tok)
   122  		So(err, ShouldBeNil)
   123  
   124  		// Some moments later is considered expired for real.
   125  		tc.Add(allowedClockSkew + time.Second)
   126  		_, err = verifyToken(tok)
   127  		So(err, ShouldErrLike, "expired")
   128  	})
   129  }