blob: 876f83f4ee928c8ee877851897086ccfc49d1a00 [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 auth
import (
"encoding/json"
"fmt"
"math/rand"
"net/http"
"testing"
"time"
"golang.org/x/net/context"
"golang.org/x/oauth2"
"github.com/luci/luci-go/common/clock"
"github.com/luci/luci-go/common/clock/testclock"
"github.com/luci/luci-go/common/data/rand/mathrand"
. "github.com/smartystreets/goconvey/convey"
)
func TestMintAccessTokenForServiceAccount(t *testing.T) {
t.Parallel()
Convey("MintAccessTokenForServiceAccount works", t, func() {
ctx := context.Background()
ctx, _ = testclock.UseTime(ctx, testclock.TestRecentTimeUTC)
ctx = mathrand.Set(ctx, rand.New(rand.NewSource(12345)))
// Create an LRU large enough that it will never cycle during test.
tokenCache := MemoryCache(1024)
returnedToken := "token1"
transport := &clientRPCTransportMock{
cb: func(r *http.Request, body string) string {
switch r.URL.String() {
// IAM request to sign the assertion.
case "https://iam.googleapis.com/v1/projects/-/serviceAccounts/abc@example.com:signJwt?alt=json":
// Check the valid claimset is being passed.
var req struct {
Payload string `json:"payload"`
}
json.Unmarshal([]byte(body), &req)
claimSet := map[string]interface{}{}
json.Unmarshal([]byte(req.Payload), &claimSet)
So(claimSet["iss"], ShouldEqual, "abc@example.com")
So(claimSet["scope"], ShouldEqual, "scope_a scope_b")
return `{"keyId":"key_id","signature":"c2lnbmF0dXJl"}`
// Exchange of the assertion for the access token.
case "https://www.googleapis.com/oauth2/v4/token":
return fmt.Sprintf(`{"access_token":"%s","token_type":"Bearer","expires_in":3600}`, returnedToken)
default:
t.Fatalf("Unexpected request to %s", r.URL)
return "unknown URL"
}
},
}
ctx = ModifyConfig(ctx, func(cfg Config) Config {
cfg.AccessTokenProvider = transport.getAccessToken
cfg.AnonymousTransport = transport.getTransport
cfg.Cache = tokenCache
return cfg
})
tok, err := MintAccessTokenForServiceAccount(ctx, MintAccessTokenParams{
ServiceAccount: "abc@example.com",
Scopes: []string{"scope_b", "scope_a"},
})
So(err, ShouldBeNil)
So(tok, ShouldResemble, &oauth2.Token{
AccessToken: "token1",
TokenType: "Bearer",
Expiry: clock.Now(ctx).Add(3600 * time.Second).UTC(),
})
// Cached now.
So(tokenCache.(*memoryCache).cache.Len(), ShouldEqual, 1)
v, _ := tokenCache.Get(ctx, "as_actor_tokens/1/b16kofTATGlqFdw3fKVf2-pyMEs")
So(v, ShouldNotBeNil)
// On subsequence request the cached token is used.
returnedToken = "token2"
tok, err = MintAccessTokenForServiceAccount(ctx, MintAccessTokenParams{
ServiceAccount: "abc@example.com",
Scopes: []string{"scope_b", "scope_a"},
})
So(err, ShouldBeNil)
So(tok.AccessToken, ShouldEqual, "token1") // old one
// Unless it expires sooner than requested TTL.
clock.Get(ctx).(testclock.TestClock).Add(40 * time.Minute)
tok, err = MintAccessTokenForServiceAccount(ctx, MintAccessTokenParams{
ServiceAccount: "abc@example.com",
Scopes: []string{"scope_b", "scope_a"},
MinTTL: 30 * time.Minute,
})
So(err, ShouldBeNil)
So(tok.AccessToken, ShouldResemble, "token2") // new one
})
}