@@ -10,16 +10,19 @@ import (
10
10
"sync"
11
11
"time"
12
12
13
+ "github.com/MicahParks/keyfunc/v3"
14
+
13
15
"github.com/Azure/go-autorest/autorest"
14
16
"github.com/Azure/go-autorest/autorest/adal"
15
- jwt "github.com/golang-jwt/jwt/v5"
17
+ "github.com/golang-jwt/jwt/v5"
16
18
17
19
"github.com/jetstack/version-checker/pkg/api"
18
20
"github.com/jetstack/version-checker/pkg/client/util"
19
21
)
20
22
21
23
const (
22
- userAgent = "jetstack/version-checker"
24
+ userAgent = "jetstack/version-checker"
25
+ requiredScope = "repository:*:metadata_read"
23
26
)
24
27
25
28
type Client struct {
@@ -38,6 +41,7 @@ type Options struct {
38
41
Username string
39
42
Password string
40
43
RefreshToken string
44
+ JWKSURI string
41
45
}
42
46
43
47
type AccessTokenResponse struct {
@@ -154,35 +158,52 @@ func (c *Client) getACRClient(ctx context.Context, host string) (*acrClient, err
154
158
}
155
159
156
160
var (
157
- client * acrClient
158
- err error
161
+ client * acrClient
162
+ accessTokenClient * autorest.Client
163
+ accessTokenReq * http.Request
164
+ err error
159
165
)
160
-
161
166
if len (c .RefreshToken ) > 0 {
162
- client , err = c .getAccessTokenClient (ctx , host )
167
+ accessTokenClient , accessTokenReq , err = c .getAccessTokenRequesterForRefreshToken (ctx , host )
163
168
} else {
164
- client , err = c .getBasicAuthClient ( host )
169
+ accessTokenClient , accessTokenReq , err = c .getAccessTokenRequesterForBasicAuth ( ctx , host )
165
170
}
166
171
if err != nil {
167
172
return nil , err
168
173
}
174
+ if client , err = c .getAuthorizedClient (accessTokenClient , accessTokenReq , host ); err != nil {
175
+ return nil , err
176
+ }
169
177
170
178
c .cachedACRClient [host ] = client
171
179
172
180
return client , nil
173
181
}
174
182
175
- func (c * Client ) getBasicAuthClient ( _ string ) (* acrClient , error ) {
183
+ func (c * Client ) getAccessTokenRequesterForBasicAuth ( ctx context. Context , host string ) (* autorest. Client , * http. Request , error ) {
176
184
client := autorest .NewClientWithUserAgent (userAgent )
177
185
client .Authorizer = autorest .NewBasicAuthorizer (c .Username , c .Password )
186
+ urlParameters := map [string ]interface {}{
187
+ "url" : "https://" + host ,
188
+ }
178
189
179
- return & acrClient {
180
- Client : & client ,
181
- tokenExpiry : time .Unix (1 << 63 - 1 , 0 ),
182
- }, nil
190
+ preparer := autorest .CreatePreparer (
191
+ autorest .WithCustomBaseURL ("{url}" , urlParameters ),
192
+ autorest .WithPath ("/oauth2/token" ),
193
+ autorest .WithQueryParameters (map [string ]interface {}{
194
+ "scope" : requiredScope ,
195
+ "service" : host ,
196
+ }),
197
+ )
198
+ req , err := preparer .Prepare ((& http.Request {}).WithContext (ctx ))
199
+ if err != nil {
200
+ return nil , nil , err
201
+ }
202
+
203
+ return & client , req , nil
183
204
}
184
205
185
- func (c * Client ) getAccessTokenClient (ctx context.Context , host string ) (* acrClient , error ) {
206
+ func (c * Client ) getAccessTokenRequesterForRefreshToken (ctx context.Context , host string ) (* autorest. Client , * http. Request , error ) {
186
207
client := autorest .NewClientWithUserAgent (userAgent )
187
208
urlParameters := map [string ]interface {}{
188
209
"url" : "https://" + host ,
@@ -191,7 +212,7 @@ func (c *Client) getAccessTokenClient(ctx context.Context, host string) (*acrCli
191
212
formDataParameters := map [string ]interface {}{
192
213
"grant_type" : "refresh_token" ,
193
214
"refresh_token" : c .RefreshToken ,
194
- "scope" : "repository:*:*" ,
215
+ "scope" : requiredScope ,
195
216
"service" : host ,
196
217
}
197
218
@@ -202,9 +223,12 @@ func (c *Client) getAccessTokenClient(ctx context.Context, host string) (*acrCli
202
223
autorest .WithFormData (autorest .MapToValues (formDataParameters )))
203
224
req , err := preparer .Prepare ((& http.Request {}).WithContext (ctx ))
204
225
if err != nil {
205
- return nil , err
226
+ return nil , nil , err
206
227
}
228
+ return & client , req , nil
229
+ }
207
230
231
+ func (c * Client ) getAuthorizedClient (client * autorest.Client , req * http.Request , host string ) (* acrClient , error ) {
208
232
resp , err := autorest .SendWithSender (client , req ,
209
233
autorest .DoRetryForStatusCodes (client .RetryAttempts , client .RetryDuration , autorest .StatusCodesForRetry ... ),
210
234
)
@@ -220,26 +244,38 @@ func (c *Client) getAccessTokenClient(ctx context.Context, host string) (*acrCli
220
244
host , err )
221
245
}
222
246
223
- exp , err := getTokenExpiration (respToken .AccessToken )
247
+ exp , err := c . getTokenExpiration (respToken .AccessToken )
224
248
if err != nil {
225
249
return nil , fmt .Errorf ("%s: %s" , host , err )
226
250
}
227
251
228
252
token := & adal.Token {
229
- RefreshToken : c . RefreshToken ,
253
+ RefreshToken : "" , // empty if access_token was retrieved with basic auth. but client is not reused after expiry anyway (see cachedACRClient)
230
254
AccessToken : respToken .AccessToken ,
231
255
}
232
256
233
257
client .Authorizer = autorest .NewBearerAuthorizer (token )
234
258
235
259
return & acrClient {
236
260
tokenExpiry : exp ,
237
- Client : & client ,
261
+ Client : client ,
238
262
}, nil
239
263
}
240
264
241
- func getTokenExpiration (tokenString string ) (time.Time , error ) {
242
- token , err := jwt .Parse (tokenString , nil , jwt .WithoutClaimsValidation ())
265
+ func (c * Client ) getTokenExpiration (tokenString string ) (time.Time , error ) {
266
+ jwtParser := jwt .NewParser (jwt .WithoutClaimsValidation ())
267
+ var token * jwt.Token
268
+ var err error
269
+ if c .JWKSURI != "" {
270
+ var k keyfunc.Keyfunc
271
+ k , err = keyfunc .NewDefaultCtx (context .TODO (), []string {c .JWKSURI })
272
+ if err != nil {
273
+ return time.Time {}, err
274
+ }
275
+ token , err = jwtParser .Parse (tokenString , k .Keyfunc )
276
+ } else {
277
+ token , _ , err = jwtParser .ParseUnverified (tokenString , jwt.MapClaims {})
278
+ }
243
279
if err != nil {
244
280
return time.Time {}, err
245
281
}
0 commit comments