@@ -10,16 +10,19 @@ import (
1010 "sync"
1111 "time"
1212
13+ "github.com/MicahParks/keyfunc/v3"
14+
1315 "github.com/Azure/go-autorest/autorest"
1416 "github.com/Azure/go-autorest/autorest/adal"
15- jwt "github.com/golang-jwt/jwt/v5"
17+ "github.com/golang-jwt/jwt/v5"
1618
1719 "github.com/jetstack/version-checker/pkg/api"
1820 "github.com/jetstack/version-checker/pkg/client/util"
1921)
2022
2123const (
22- userAgent = "jetstack/version-checker"
24+ userAgent = "jetstack/version-checker"
25+ requiredScope = "repository:*:metadata_read"
2326)
2427
2528type Client struct {
@@ -38,6 +41,7 @@ type Options struct {
3841 Username string
3942 Password string
4043 RefreshToken string
44+ JWKSURI string
4145}
4246
4347type AccessTokenResponse struct {
@@ -154,35 +158,52 @@ func (c *Client) getACRClient(ctx context.Context, host string) (*acrClient, err
154158 }
155159
156160 var (
157- client * acrClient
158- err error
161+ client * acrClient
162+ accessTokenClient * autorest.Client
163+ accessTokenReq * http.Request
164+ err error
159165 )
160-
161166 if len (c .RefreshToken ) > 0 {
162- client , err = c .getAccessTokenClient (ctx , host )
167+ accessTokenClient , accessTokenReq , err = c .getAccessTokenRequesterForRefreshToken (ctx , host )
163168 } else {
164- client , err = c .getBasicAuthClient ( host )
169+ accessTokenClient , accessTokenReq , err = c .getAccessTokenRequesterForBasicAuth ( ctx , host )
165170 }
166171 if err != nil {
167172 return nil , err
168173 }
174+ if client , err = c .getAuthorizedClient (accessTokenClient , accessTokenReq , host ); err != nil {
175+ return nil , err
176+ }
169177
170178 c .cachedACRClient [host ] = client
171179
172180 return client , nil
173181}
174182
175- func (c * Client ) getBasicAuthClient ( _ string ) (* acrClient , error ) {
183+ func (c * Client ) getAccessTokenRequesterForBasicAuth ( ctx context. Context , host string ) (* autorest. Client , * http. Request , error ) {
176184 client := autorest .NewClientWithUserAgent (userAgent )
177185 client .Authorizer = autorest .NewBasicAuthorizer (c .Username , c .Password )
186+ urlParameters := map [string ]interface {}{
187+ "url" : "https://" + host ,
188+ }
178189
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
183204}
184205
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 ) {
186207 client := autorest .NewClientWithUserAgent (userAgent )
187208 urlParameters := map [string ]interface {}{
188209 "url" : "https://" + host ,
@@ -191,7 +212,7 @@ func (c *Client) getAccessTokenClient(ctx context.Context, host string) (*acrCli
191212 formDataParameters := map [string ]interface {}{
192213 "grant_type" : "refresh_token" ,
193214 "refresh_token" : c .RefreshToken ,
194- "scope" : "repository:*:*" ,
215+ "scope" : requiredScope ,
195216 "service" : host ,
196217 }
197218
@@ -202,9 +223,12 @@ func (c *Client) getAccessTokenClient(ctx context.Context, host string) (*acrCli
202223 autorest .WithFormData (autorest .MapToValues (formDataParameters )))
203224 req , err := preparer .Prepare ((& http.Request {}).WithContext (ctx ))
204225 if err != nil {
205- return nil , err
226+ return nil , nil , err
206227 }
228+ return & client , req , nil
229+ }
207230
231+ func (c * Client ) getAuthorizedClient (client * autorest.Client , req * http.Request , host string ) (* acrClient , error ) {
208232 resp , err := autorest .SendWithSender (client , req ,
209233 autorest .DoRetryForStatusCodes (client .RetryAttempts , client .RetryDuration , autorest .StatusCodesForRetry ... ),
210234 )
@@ -220,26 +244,38 @@ func (c *Client) getAccessTokenClient(ctx context.Context, host string) (*acrCli
220244 host , err )
221245 }
222246
223- exp , err := getTokenExpiration (respToken .AccessToken )
247+ exp , err := c . getTokenExpiration (respToken .AccessToken )
224248 if err != nil {
225249 return nil , fmt .Errorf ("%s: %s" , host , err )
226250 }
227251
228252 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)
230254 AccessToken : respToken .AccessToken ,
231255 }
232256
233257 client .Authorizer = autorest .NewBearerAuthorizer (token )
234258
235259 return & acrClient {
236260 tokenExpiry : exp ,
237- Client : & client ,
261+ Client : client ,
238262 }, nil
239263}
240264
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+ }
243279 if err != nil {
244280 return time.Time {}, err
245281 }
0 commit comments