go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/auth/scoped_test.go (about) 1 // Copyright 2019 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 auth 16 17 import ( 18 "context" 19 "testing" 20 "time" 21 22 "google.golang.org/grpc" 23 "google.golang.org/grpc/codes" 24 "google.golang.org/grpc/status" 25 "google.golang.org/protobuf/types/known/timestamppb" 26 27 "go.chromium.org/luci/common/clock" 28 "go.chromium.org/luci/common/clock/testclock" 29 "go.chromium.org/luci/tokenserver/api/minter/v1" 30 31 "go.chromium.org/luci/server/caching" 32 33 . "github.com/smartystreets/goconvey/convey" 34 ) 35 36 type scopedTokenMinterMock struct { 37 request minter.MintProjectTokenRequest 38 response minter.MintProjectTokenResponse 39 err error 40 } 41 42 func (m *scopedTokenMinterMock) MintProjectToken(ctx context.Context, in *minter.MintProjectTokenRequest, opts ...grpc.CallOption) (*minter.MintProjectTokenResponse, error) { 43 m.request = *in 44 if m.err != nil { 45 return nil, m.err 46 } 47 return &m.response, nil 48 } 49 50 func TestMintServiceOAuthToken(t *testing.T) { 51 t.Parallel() 52 53 Convey("MintProjectToken works", t, func() { 54 ctx := context.Background() 55 ctx, tc := testclock.UseTime(ctx, testclock.TestRecentTimeUTC) 56 ctx = caching.WithEmptyProcessCache(ctx) 57 ctx = Initialize(ctx, &Config{}) 58 59 mockedClient := &scopedTokenMinterMock{ 60 response: minter.MintProjectTokenResponse{ 61 ServiceAccountEmail: "foobarserviceaccount", 62 AccessToken: "tok", 63 Expiry: timestamppb.New(clock.Now(ctx).Add(MaxScopedTokenTTL)), 64 }, 65 } 66 67 ctx = WithState(ctx, &state{ 68 user: &User{Identity: "user:abc@example.com"}, 69 db: &fakeDB{tokenServiceURL: "https://tokens.example.com"}, 70 }) 71 72 Convey("Works (including caching)", func(c C) { 73 tok, err := MintProjectToken(ctx, ProjectTokenParams{ 74 MinTTL: 10 * time.Minute, 75 rpcClient: mockedClient, 76 LuciProject: "infra", 77 OAuthScopes: defaultOAuthScopes, 78 }) 79 So(err, ShouldBeNil) 80 So(tok, ShouldResemble, &Token{ 81 Token: "tok", 82 Expiry: testclock.TestRecentTimeUTC.Add(MaxScopedTokenTTL).Truncate(time.Second), 83 }) 84 So(mockedClient.request, ShouldResemble, minter.MintProjectTokenRequest{ 85 LuciProject: "infra", 86 OauthScope: defaultOAuthScopes, 87 MinValidityDuration: 900, 88 }) 89 90 // Cached now. 91 So(scopedTokenCache.lc.CachedLocally(ctx), ShouldEqual, 1) 92 93 // On subsequence request the cached token is used. 94 mockedClient.response.AccessToken = "another token" 95 tok, err = MintProjectToken(ctx, ProjectTokenParams{ 96 MinTTL: 10 * time.Minute, 97 rpcClient: mockedClient, 98 LuciProject: "infra", 99 OAuthScopes: defaultOAuthScopes, 100 }) 101 So(err, ShouldBeNil) 102 So(tok.Token, ShouldResemble, "tok") // old one 103 104 // Unless it expires sooner than requested TTL. 105 rollTimeForward := MaxDelegationTokenTTL - 30*time.Minute 106 clock.Get(ctx).(testclock.TestClock).Add(rollTimeForward) 107 mockedClient.response.Expiry = timestamppb.New(clock.Now(ctx).Add(MaxScopedTokenTTL)) 108 109 tok, err = MintProjectToken(ctx, ProjectTokenParams{ 110 MinTTL: 10 * time.Minute, 111 rpcClient: mockedClient, 112 LuciProject: "infra", 113 OAuthScopes: defaultOAuthScopes, 114 }) 115 So(err, ShouldBeNil) 116 So(tok.Token, ShouldResemble, "another token") // new one 117 }) 118 119 Convey("Project scoped fallback works (including caching)", func(c C) { 120 mockedClient = &scopedTokenMinterMock{ 121 response: minter.MintProjectTokenResponse{}, 122 err: status.Errorf(codes.NotFound, "unable to find project identity for project"), 123 } 124 125 tok, err := MintProjectToken(ctx, ProjectTokenParams{ 126 MinTTL: 4 * time.Minute, 127 rpcClient: mockedClient, 128 LuciProject: "infra", 129 OAuthScopes: defaultOAuthScopes, 130 }) 131 So(err, ShouldBeNil) 132 So(tok, ShouldBeNil) 133 134 // On subsequence request the cached token is used. 135 mockedClient.response = minter.MintProjectTokenResponse{ 136 ServiceAccountEmail: "foobarserviceaccount", 137 AccessToken: "tok", 138 Expiry: timestamppb.New(clock.Now(ctx).Add(MaxScopedTokenTTL)), 139 } 140 mockedClient.err = nil 141 tok, err = MintProjectToken(ctx, ProjectTokenParams{ 142 MinTTL: 4 * time.Minute, 143 rpcClient: mockedClient, 144 LuciProject: "infra", 145 OAuthScopes: defaultOAuthScopes, 146 }) 147 So(err, ShouldBeNil) 148 So(tok, ShouldBeNil) 149 150 // However requesting for another project produces a different result 151 mockedClient.response = minter.MintProjectTokenResponse{ 152 ServiceAccountEmail: "foobarserviceaccount", 153 AccessToken: "tok", 154 Expiry: timestamppb.New(clock.Now(ctx).Add(MaxScopedTokenTTL)), 155 } 156 mockedClient.err = nil 157 tok, err = MintProjectToken(ctx, ProjectTokenParams{ 158 MinTTL: 4 * time.Minute, 159 rpcClient: mockedClient, 160 LuciProject: "infra-experimental", 161 OAuthScopes: defaultOAuthScopes, 162 }) 163 164 So(err, ShouldBeNil) 165 So(tok, ShouldNotBeNil) 166 167 // Simulate cache expiry, check that a new token attempt is sent out 168 tc.Add(5 * time.Minute) 169 tok, err = MintProjectToken(ctx, ProjectTokenParams{ 170 MinTTL: 4 * time.Minute, 171 rpcClient: mockedClient, 172 LuciProject: "infra", 173 OAuthScopes: defaultOAuthScopes, 174 }) 175 So(err, ShouldBeNil) 176 So(tok.Token, ShouldResemble, "tok") 177 }) 178 }) 179 }