blob: 2e4c3887e70eb26e98f3c096b930650db1f34388 [file] [log] [blame]
// Copyright 2015 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 auth
import (
"context"
"io/ioutil"
"math/rand"
"net/http"
"net/http/httptest"
"testing"
"time"
"golang.org/x/oauth2"
"go.chromium.org/luci/auth/internal"
"go.chromium.org/luci/common/clock"
"go.chromium.org/luci/common/clock/testclock"
"go.chromium.org/luci/common/data/rand/mathrand"
"go.chromium.org/luci/common/errors"
"go.chromium.org/luci/common/retry/transient"
. "github.com/smartystreets/goconvey/convey"
. "go.chromium.org/luci/common/testing/assertions"
)
var (
now = time.Date(2015, time.January, 1, 0, 0, 0, 0, time.UTC)
past = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC)
future = now.Add(24 * time.Hour)
)
func TestTransportFactory(t *testing.T) {
t.Parallel()
Convey("InteractiveLogin + interactive provider: invokes Login", t, func() {
provider := &fakeTokenProvider{
interactive: true,
}
auth, _ := newAuth(InteractiveLogin, provider, nil, "")
// Returns "hooked" transport, not default.
//
// Note: we don't use ShouldNotEqual because it tries to read guts of
// http.DefaultTransport and it sometimes triggers race detector.
t, err := auth.Transport()
So(err, ShouldBeNil)
So(t != http.DefaultTransport, ShouldBeTrue)
// MintToken is called by Login.
So(provider.mintTokenCalled, ShouldBeTrue)
})
Convey("SilentLogin + interactive provider: ErrLoginRequired", t, func() {
auth, _ := newAuth(SilentLogin, &fakeTokenProvider{
interactive: true,
}, nil, "")
_, err := auth.Transport()
So(err, ShouldEqual, ErrLoginRequired)
})
Convey("OptionalLogin + interactive provider: Fallback to non-auth", t, func() {
auth, _ := newAuth(OptionalLogin, &fakeTokenProvider{
interactive: true,
}, nil, "")
t, err := auth.Transport()
So(err, ShouldBeNil)
So(t == http.DefaultTransport, ShouldBeTrue)
})
Convey("Always uses authenticating transport for non-interactive provider", t, func() {
modes := []LoginMode{InteractiveLogin, SilentLogin, OptionalLogin}
for _, mode := range modes {
auth, _ := newAuth(mode, &fakeTokenProvider{}, nil, "")
So(auth.Login(), ShouldBeNil) // noop
t, err := auth.Transport()
So(err, ShouldBeNil)
So(t != http.DefaultTransport, ShouldBeTrue)
}
})
}
func TestRefreshToken(t *testing.T) {
t.Parallel()
Convey("Test non-interactive auth (no cache)", t, func() {
tokenProvider := &fakeTokenProvider{
interactive: false,
tokenToMint: &internal.Token{
Token: oauth2.Token{AccessToken: "minted"},
Email: "freshly-minted@example.com",
},
}
auth, _ := newAuth(SilentLogin, tokenProvider, nil, "")
So(auth.CheckLoginRequired(), ShouldBeNil)
// No token yet, it is is lazily loaded below.
tok, err := auth.currentToken()
So(err, ShouldBeNil)
So(tok, ShouldBeNil)
// The token is minted on first request.
oauthTok, err := auth.GetAccessToken(time.Minute)
So(err, ShouldBeNil)
So(oauthTok.AccessToken, ShouldEqual, "minted")
// And we also get an email straight from MintToken call.
email, err := auth.GetEmail()
So(err, ShouldBeNil)
So(email, ShouldEqual, "freshly-minted@example.com")
})
Convey("Test non-interactive auth (with non-expired cache)", t, func() {
tokenProvider := &fakeTokenProvider{
interactive: false,
}
auth, _ := newAuth(SilentLogin, tokenProvider, nil, "")
cacheToken(auth, tokenProvider, &internal.Token{
Token: oauth2.Token{
AccessToken: "cached",
Expiry: future,
},
Email: "cached-email@example.com",
})
So(auth.CheckLoginRequired(), ShouldBeNil)
// Cached token is used.
oauthTok, err := auth.GetAccessToken(time.Minute)
So(err, ShouldBeNil)
So(oauthTok.AccessToken, ShouldEqual, "cached")
// Cached email is used.
email, err := auth.GetEmail()
So(err, ShouldBeNil)
So(email, ShouldEqual, "cached-email@example.com")
})
Convey("Test non-interactive auth (with expired cache)", t, func() {
tokenProvider := &fakeTokenProvider{
interactive: false,
tokenToRefresh: &internal.Token{
Token: oauth2.Token{AccessToken: "refreshed"},
Email: "new-email@example.com",
},
}
auth, _ := newAuth(SilentLogin, tokenProvider, nil, "")
cacheToken(auth, tokenProvider, &internal.Token{
Token: oauth2.Token{
AccessToken: "cached",
Expiry: past,
},
Email: "cached-email@example.com",
})
So(auth.CheckLoginRequired(), ShouldBeNil)
// The usage triggers refresh procedure.
oauthTok, err := auth.GetAccessToken(time.Minute)
So(err, ShouldBeNil)
So(oauthTok.AccessToken, ShouldEqual, "refreshed")
// Using a newly fetched email.
email, err := auth.GetEmail()
So(err, ShouldBeNil)
So(email, ShouldEqual, "new-email@example.com")
})
Convey("Test interactive auth (no cache)", t, func() {
tokenProvider := &fakeTokenProvider{
interactive: true,
tokenToMint: &internal.Token{
Token: oauth2.Token{AccessToken: "minted"},
Email: "freshly-minted@example.com",
},
}
auth, _ := newAuth(SilentLogin, tokenProvider, nil, "")
// No token cached.
tok, err := auth.currentToken()
So(err, ShouldBeNil)
So(tok, ShouldBeNil)
// Login is required, as reported by various methods.
So(auth.CheckLoginRequired(), ShouldEqual, ErrLoginRequired)
oauthTok, err := auth.GetAccessToken(time.Minute)
So(oauthTok, ShouldBeNil)
So(err, ShouldEqual, ErrLoginRequired)
email, err := auth.GetEmail()
So(email, ShouldEqual, "")
So(err, ShouldEqual, ErrLoginRequired)
// Do it.
err = auth.Login()
So(err, ShouldBeNil)
So(auth.CheckLoginRequired(), ShouldBeNil)
// Minted initial token.
tok, err = auth.currentToken()
So(err, ShouldBeNil)
So(tok.AccessToken, ShouldEqual, "minted")
// And it is actually used.
oauthTok, err = auth.GetAccessToken(time.Minute)
So(err, ShouldBeNil)
So(oauthTok.AccessToken, ShouldEqual, "minted")
// Email works too now.
email, err = auth.GetEmail()
So(err, ShouldBeNil)
So(email, ShouldEqual, "freshly-minted@example.com")
})
Convey("Test interactive auth (with non-expired cache)", t, func() {
tokenProvider := &fakeTokenProvider{
interactive: true,
}
auth, _ := newAuth(SilentLogin, tokenProvider, nil, "")
cacheToken(auth, tokenProvider, &internal.Token{
Token: oauth2.Token{
AccessToken: "cached",
Expiry: future,
},
Email: "cached-email@example.com",
})
// No need to login, already have a token.
So(auth.CheckLoginRequired(), ShouldBeNil)
// Loaded cached token.
tok, err := auth.currentToken()
So(err, ShouldBeNil)
So(tok.AccessToken, ShouldEqual, "cached")
// And it is actually used.
oauthTok, err := auth.GetAccessToken(time.Minute)
So(err, ShouldBeNil)
So(oauthTok.AccessToken, ShouldEqual, "cached")
// Email works too now.
email, err := auth.GetEmail()
So(err, ShouldBeNil)
So(email, ShouldEqual, "cached-email@example.com")
})
Convey("Test interactive auth (with expired cache)", t, func() {
tokenProvider := &fakeTokenProvider{
interactive: true,
tokenToRefresh: &internal.Token{
Token: oauth2.Token{AccessToken: "refreshed"},
Email: "refreshed-email@example.com",
},
}
auth, _ := newAuth(SilentLogin, tokenProvider, nil, "")
cacheToken(auth, tokenProvider, &internal.Token{
Token: oauth2.Token{
AccessToken: "cached",
Expiry: past,
},
Email: "cached-email@example.com",
})
// No need to login, already have a token. Only its "access_token" part is
// expired. Refresh token part is still valid, so no login is required.
So(auth.CheckLoginRequired(), ShouldBeNil)
// Loaded cached token.
tok, err := auth.currentToken()
So(err, ShouldBeNil)
So(tok.AccessToken, ShouldEqual, "cached")
// Attempting to use it triggers a refresh.
oauthTok, err := auth.GetAccessToken(time.Minute)
So(err, ShouldBeNil)
So(oauthTok.AccessToken, ShouldEqual, "refreshed")
// Email is also refreshed.
email, err := auth.GetEmail()
So(err, ShouldBeNil)
So(email, ShouldEqual, "refreshed-email@example.com")
})
Convey("Test revoked refresh_token", t, func() {
tokenProvider := &fakeTokenProvider{
interactive: true,
revokedToken: true,
}
auth, _ := newAuth(SilentLogin, tokenProvider, nil, "")
cacheToken(auth, tokenProvider, &internal.Token{
Token: oauth2.Token{
AccessToken: "cached",
Expiry: past,
},
Email: "cached@example.com",
})
// No need to login, already have a token. Only its "access_token" part is
// expired. Refresh token part is still presumably valid, there's no way to
// detect that it has been revoked without attempting to use it.
So(auth.CheckLoginRequired(), ShouldBeNil)
// Loaded cached token.
tok, err := auth.currentToken()
So(err, ShouldBeNil)
So(tok.AccessToken, ShouldEqual, "cached")
// Attempting to use it triggers a refresh that fails.
_, err = auth.GetAccessToken(time.Minute)
So(err, ShouldEqual, ErrLoginRequired)
// Same happens when trying to grab an email.
_, err = auth.GetEmail()
So(err, ShouldEqual, ErrLoginRequired)
})
Convey("Test revoked credentials", t, func() {
tokenProvider := &fakeTokenProvider{
interactive: false,
revokedCreds: true,
}
auth, _ := newAuth(SilentLogin, tokenProvider, nil, "")
cacheToken(auth, tokenProvider, &internal.Token{
Token: oauth2.Token{
AccessToken: "cached",
Expiry: past,
},
Email: "cached@example.com",
})
So(auth.CheckLoginRequired(), ShouldBeNil)
// Attempting to use expired cached token triggers a refresh that fails.
_, err := auth.GetAccessToken(time.Minute)
So(err, ShouldEqual, ErrBadCredentials)
// Same happens when trying to grab an email.
_, err = auth.GetEmail()
So(err, ShouldEqual, ErrBadCredentials)
})
Convey("Test transient errors when refreshing, success", t, func() {
tokenProvider := &fakeTokenProvider{
interactive: false,
transientRefreshErrors: 5,
tokenToRefresh: &internal.Token{
Token: oauth2.Token{AccessToken: "refreshed"},
},
}
auth, _ := newAuth(SilentLogin, tokenProvider, nil, "")
cacheToken(auth, tokenProvider, &internal.Token{
Token: oauth2.Token{
AccessToken: "cached",
Expiry: past,
},
})
So(auth.CheckLoginRequired(), ShouldBeNil)
// Attempting to use expired cached token triggers a refresh that fails a
// bunch of times, but the succeeds.
tok, err := auth.GetAccessToken(time.Minute)
So(err, ShouldBeNil)
So(tok.AccessToken, ShouldEqual, "refreshed")
// All calls were actually made.
So(tokenProvider.transientRefreshErrors, ShouldEqual, 0)
})
Convey("Test transient errors when refreshing, timeout", t, func() {
tokenProvider := &fakeTokenProvider{
interactive: false,
transientRefreshErrors: 5000, // never succeeds
}
auth, ctx := newAuth(SilentLogin, tokenProvider, nil, "")
cacheToken(auth, tokenProvider, &internal.Token{
Token: oauth2.Token{
AccessToken: "cached",
Expiry: past,
},
})
So(auth.CheckLoginRequired(), ShouldBeNil)
// Attempting to use expired cached token triggers a refresh that constantly
// fails. Eventually we give up.
before := clock.Now(ctx)
_, err := auth.GetAccessToken(time.Minute)
So(err, ShouldErrLike, "transient error")
after := clock.Now(ctx)
// It took reasonable amount of time and number of attempts.
So(after.Sub(before), ShouldBeLessThan, 4*time.Minute)
So(5000-tokenProvider.transientRefreshErrors, ShouldEqual, 15)
})
}
func TestActorMode(t *testing.T) {
t.Parallel()
Convey("Test non-interactive auth (no cache)", t, func() {
baseProvider := &fakeTokenProvider{
interactive: false,
tokenToMint: &internal.Token{
Token: oauth2.Token{
AccessToken: "minted-base",
Expiry: now.Add(time.Hour),
},
Email: "must-be-ignored@example.com",
},
tokenToRefresh: &internal.Token{
Token: oauth2.Token{
AccessToken: "refreshed-base",
Expiry: now.Add(2 * time.Hour),
},
Email: "must-be-ignored@example.com",
},
}
iamProvider := &fakeTokenProvider{
interactive: false,
tokenToMint: &internal.Token{
Token: oauth2.Token{
AccessToken: "minted-iam",
Expiry: now.Add(30 * time.Minute),
},
Email: "minted-iam@example.com",
},
tokenToRefresh: &internal.Token{
Token: oauth2.Token{
AccessToken: "refreshed-iam",
Expiry: now.Add(2 * time.Hour),
},
Email: "refreshed-iam@example.com",
},
}
auth, ctx := newAuth(SilentLogin, baseProvider, iamProvider, "as-actor")
So(auth.CheckLoginRequired(), ShouldBeNil)
// No token yet, it is is lazily loaded below.
tok, err := auth.currentToken()
So(err, ShouldBeNil)
So(tok, ShouldBeNil)
// The token is minted on the first request. It is IAM-derived token.
oauthTok, err := auth.GetAccessToken(time.Minute)
So(err, ShouldBeNil)
So(oauthTok.AccessToken, ShouldEqual, "minted-iam")
// The email also matches the IAM token.
email, err := auth.GetEmail()
So(err, ShouldBeNil)
So(email, ShouldEqual, "minted-iam@example.com")
// The correct base token was minted as well and used by IAM call.
So(iamProvider.baseTokenInMint.AccessToken, ShouldEqual, "minted-base")
iamProvider.baseTokenInMint = nil
// After 40 min the IAM-generated token expires, but base is still ok.
clock.Get(ctx).(testclock.TestClock).Add(40 * time.Minute)
// Getting a refreshed IAM token.
oauthTok, err = auth.GetAccessToken(time.Minute)
So(err, ShouldBeNil)
So(oauthTok.AccessToken, ShouldEqual, "refreshed-iam")
// The email also matches the IAM token.
email, err = auth.GetEmail()
So(err, ShouldBeNil)
So(email, ShouldEqual, "refreshed-iam@example.com")
// Using existing base token (still valid).
So(iamProvider.baseTokenInRefresh.AccessToken, ShouldEqual, "minted-base")
iamProvider.baseTokenInRefresh = nil
})
}
func TestTransport(t *testing.T) {
t.Parallel()
Convey("Test transport works", t, func(c C) {
calls := 0
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
calls++
switch r.URL.Path {
case "/1":
c.So(r.Header.Get("Authorization"), ShouldEqual, "Bearer minted")
case "/2":
c.So(r.Header.Get("Authorization"), ShouldEqual, "Bearer minted")
case "/3":
c.So(r.Header.Get("Authorization"), ShouldEqual, "Bearer refreshed")
default:
c.So(r.URL.Path, ShouldBeBlank) // just fail in some helpful way
}
w.WriteHeader(200)
}))
defer ts.Close()
tokenProvider := &fakeTokenProvider{
interactive: false,
tokenToMint: &internal.Token{
Token: oauth2.Token{AccessToken: "minted", Expiry: now.Add(time.Hour)},
},
tokenToRefresh: &internal.Token{
Token: oauth2.Token{AccessToken: "refreshed", Expiry: now.Add(2 * time.Hour)},
},
}
auth, ctx := newAuth(SilentLogin, tokenProvider, nil, "")
client, err := auth.Client()
So(err, ShouldBeNil)
So(client, ShouldNotBeNil)
// Initial call will mint new token.
resp, err := client.Get(ts.URL + "/1")
So(err, ShouldBeNil)
ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
// Minted token is now cached.
tok, err := auth.currentToken()
So(err, ShouldBeNil)
So(tok.AccessToken, ShouldEqual, "minted")
cacheKey, _ := tokenProvider.CacheKey(ctx)
cached, err := auth.opts.testingCache.GetToken(cacheKey)
So(err, ShouldBeNil)
So(cached.AccessToken, ShouldEqual, "minted")
// 40 minutes later it is still OK to use.
clock.Get(ctx).(testclock.TestClock).Add(40 * time.Minute)
resp, err = client.Get(ts.URL + "/2")
So(err, ShouldBeNil)
ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
// 30 min later (70 min since the start) it is expired and refreshed.
clock.Get(ctx).(testclock.TestClock).Add(30 * time.Minute)
resp, err = client.Get(ts.URL + "/3")
So(err, ShouldBeNil)
ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
tok, err = auth.currentToken()
So(err, ShouldBeNil)
So(tok.AccessToken, ShouldEqual, "refreshed")
// All calls are actually made.
So(calls, ShouldEqual, 3)
})
}
func TestOptionalLogin(t *testing.T) {
t.Parallel()
Convey("Test optional login works", t, func(c C) {
// This test simulates following scenario for OptionalLogin mode:
// 1. There's existing cached access token.
// 2. At some point it expires.
// 3. Refresh fails with ErrBadRefreshToken (refresh token is revoked).
// 4. Authenticator switches to anonymous calls.
calls := 0
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
calls++
switch r.URL.Path {
case "/1":
c.So(r.Header.Get("Authorization"), ShouldEqual, "Bearer cached")
case "/2":
c.So(r.Header.Get("Authorization"), ShouldEqual, "")
default:
c.So(r.URL.Path, ShouldBeBlank) // just fail in some helpful way
}
w.WriteHeader(200)
}))
defer ts.Close()
tokenProvider := &fakeTokenProvider{
interactive: true,
revokedToken: true,
}
auth, ctx := newAuth(OptionalLogin, tokenProvider, nil, "")
cacheToken(auth, tokenProvider, &internal.Token{
Token: oauth2.Token{
AccessToken: "cached",
Expiry: now.Add(time.Hour),
},
})
client, err := auth.Client()
So(err, ShouldBeNil)
So(client, ShouldNotBeNil)
// Initial call uses existing cached token.
resp, err := client.Get(ts.URL + "/1")
So(err, ShouldBeNil)
ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
// It expires at ~60 minutes, refresh fails, authenticator switches to
// anonymous access.
clock.Get(ctx).(testclock.TestClock).Add(65 * time.Minute)
resp, err = client.Get(ts.URL + "/2")
So(err, ShouldBeNil)
ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
// Bad token is removed from the cache.
tok, err := auth.currentToken()
So(err, ShouldBeNil)
So(tok, ShouldBeNil)
cacheKey, _ := tokenProvider.CacheKey(ctx)
cached, err := auth.opts.testingCache.GetToken(cacheKey)
So(cached, ShouldBeNil)
So(err, ShouldBeNil)
// All calls are actually made.
So(calls, ShouldEqual, 2)
})
}
func TestGetEmail(t *testing.T) {
t.Parallel()
Convey("Test non-interactive auth (no cache)", t, func() {
tokenProvider := &fakeTokenProvider{
interactive: false,
knownEmail: "known-email@example.com",
tokenToMint: &internal.Token{
Token: oauth2.Token{AccessToken: "must-not-be-called"},
Email: "must-not-be-called@example.com",
},
}
auth, _ := newAuth(SilentLogin, tokenProvider, nil, "")
// No cached token.
tok, err := auth.currentToken()
So(err, ShouldBeNil)
So(tok, ShouldBeNil)
// We get the email directly from the provider.
email, err := auth.GetEmail()
So(err, ShouldBeNil)
So(email, ShouldEqual, "known-email@example.com")
// MintToken was NOT called.
So(tokenProvider.mintTokenCalled, ShouldBeFalse)
})
Convey("Non-expired cache without email is upgraded", t, func() {
tokenProvider := &fakeTokenProvider{
interactive: true,
}
auth, _ := newAuth(SilentLogin, tokenProvider, nil, "")
cacheToken(auth, tokenProvider, &internal.Token{
Token: oauth2.Token{
AccessToken: "cached",
Expiry: future,
},
Email: "", // "old style" cache without an email
})
// No need to login, already have a token.
So(auth.CheckLoginRequired(), ShouldBeNil)
// GetAccessToken returns the cached token.
oauthTok, err := auth.GetAccessToken(time.Minute)
So(err, ShouldBeNil)
So(oauthTok.AccessToken, ShouldEqual, "cached")
// But getting an email triggers a refresh, since the cached token doesn't
// have an email.
email, err := auth.GetEmail()
So(err, ShouldBeNil)
So(email, ShouldEqual, "some-email-refreshtoken@example.com")
// GetAccessToken picks up the refreshed token too.
oauthTok, err = auth.GetAccessToken(time.Minute)
So(err, ShouldBeNil)
So(oauthTok.AccessToken, ShouldEqual, "some refreshed access token")
})
Convey("No email triggers ErrNoEmail", t, func() {
tokenProvider := &fakeTokenProvider{
interactive: false,
tokenToMint: &internal.Token{
Token: oauth2.Token{AccessToken: "minted"},
Email: internal.NoEmail,
},
}
auth, _ := newAuth(SilentLogin, tokenProvider, nil, "")
So(auth.CheckLoginRequired(), ShouldBeNil)
// The token is minted on first request.
oauthTok, err := auth.GetAccessToken(time.Minute)
So(err, ShouldBeNil)
So(oauthTok.AccessToken, ShouldEqual, "minted")
// But getting an email fails with ErrNoEmail.
email, err := auth.GetEmail()
So(err, ShouldEqual, ErrNoEmail)
So(email, ShouldEqual, "")
})
}
func TestNormalizeScopes(t *testing.T) {
t.Parallel()
checkExactSameSlice := func(a, b []string) {
So(a, ShouldResemble, b)
So(&a[0], ShouldEqual, &b[0])
}
Convey("Works", t, func() {
So(normalizeScopes(nil), ShouldBeNil)
// Doesn't copy already normalized slices.
slice := []string{"a"}
checkExactSameSlice(slice, normalizeScopes(slice))
slice = []string{"a", "b"}
checkExactSameSlice(slice, normalizeScopes(slice))
slice = []string{"a", "b", "c"}
checkExactSameSlice(slice, normalizeScopes(slice))
// Removes dups and sorts.
So(normalizeScopes([]string{"b", "a"}), ShouldResemble, []string{"a", "b"})
So(normalizeScopes([]string{"a", "a"}), ShouldResemble, []string{"a"})
So(normalizeScopes([]string{"a", "b", "a"}), ShouldResemble, []string{"a", "b"})
})
}
func newAuth(loginMode LoginMode, base, iam internal.TokenProvider, actAs string) (*Authenticator, context.Context) {
// Use auto-advancing fake time.
ctx := mathrand.Set(context.Background(), rand.New(rand.NewSource(123)))
ctx, tc := testclock.UseTime(ctx, now)
tc.SetTimerCallback(func(d time.Duration, t clock.Timer) {
tc.Add(d)
})
a := NewAuthenticator(ctx, loginMode, Options{
ActAsServiceAccount: actAs,
testingCache: &internal.MemoryTokenCache{},
testingBaseTokenProvider: base,
testingIAMTokenProvider: iam,
})
return a, ctx
}
func cacheToken(a *Authenticator, p internal.TokenProvider, tok *internal.Token) {
cacheKey, err := p.CacheKey(a.ctx)
if err != nil {
panic(err)
}
err = a.opts.testingCache.PutToken(cacheKey, tok)
if err != nil {
panic(err)
}
}
////////////////////////////////////////////////////////////////////////////////
type fakeTokenProvider struct {
interactive bool
revokedCreds bool
revokedToken bool
transientRefreshErrors int
tokenToMint *internal.Token
tokenToRefresh *internal.Token
mintTokenCalled bool
refreshTokenCalled bool
useIDTokens bool
baseTokenInMint *internal.Token
baseTokenInRefresh *internal.Token
knownEmail string
}
func (p *fakeTokenProvider) RequiresInteraction() bool {
return p.interactive
}
func (p *fakeTokenProvider) Lightweight() bool {
return true
}
func (p *fakeTokenProvider) Email() string {
return p.knownEmail
}
func (p *fakeTokenProvider) CacheKey(ctx context.Context) (*internal.CacheKey, error) {
return &internal.CacheKey{Key: "fake"}, nil
}
func (p *fakeTokenProvider) MintToken(ctx context.Context, base *internal.Token) (*internal.Token, error) {
p.mintTokenCalled = true
p.baseTokenInMint = base
if p.revokedCreds {
return nil, internal.ErrBadCredentials
}
if p.tokenToMint != nil {
return p.tokenToMint, nil
}
idTok := internal.NoIDToken
accessTok := internal.NoAccessToken
if p.useIDTokens {
idTok = "some minted ID token"
} else {
accessTok = "some minted access token"
}
return &internal.Token{
Token: oauth2.Token{AccessToken: accessTok},
IDToken: idTok,
Email: "some-email-minttoken@example.com",
}, nil
}
func (p *fakeTokenProvider) RefreshToken(ctx context.Context, prev, base *internal.Token) (*internal.Token, error) {
p.refreshTokenCalled = true
p.baseTokenInRefresh = base
if p.transientRefreshErrors != 0 {
p.transientRefreshErrors--
return nil, errors.New("transient error", transient.Tag)
}
if p.revokedCreds {
return nil, internal.ErrBadCredentials
}
if p.revokedToken {
return nil, internal.ErrBadRefreshToken
}
if p.tokenToRefresh != nil {
return p.tokenToRefresh, nil
}
idTok := internal.NoIDToken
accessTok := internal.NoAccessToken
if p.useIDTokens {
idTok = "some refreshed ID token"
} else {
accessTok = "some refreshed access token"
}
return &internal.Token{
Token: oauth2.Token{AccessToken: accessTok},
IDToken: idTok,
Email: "some-email-refreshtoken@example.com",
}, nil
}