| // Copyright 2023 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package externalaccountauthorizeduser |
| |
| import ( |
| "context" |
| "errors" |
| "time" |
| |
| "golang.org/x/oauth2" |
| "golang.org/x/oauth2/google/internal/stsexchange" |
| ) |
| |
| // now aliases time.Now for testing. |
| var now = func() time.Time { |
| return time.Now().UTC() |
| } |
| |
| var tokenValid = func(token oauth2.Token) bool { |
| return token.Valid() |
| } |
| |
| type Config struct { |
| // Audience is the Secure Token Service (STS) audience which contains the resource name for the workforce pool and |
| // the provider identifier in that pool. |
| Audience string |
| // RefreshToken is the optional OAuth 2.0 refresh token. If specified, credentials can be refreshed. |
| RefreshToken string |
| // TokenURL is the optional STS token exchange endpoint for refresh. Must be specified for refresh, can be left as |
| // None if the token can not be refreshed. |
| TokenURL string |
| // TokenInfoURL is the optional STS endpoint URL for token introspection. |
| TokenInfoURL string |
| // ClientID is only required in conjunction with ClientSecret, as described above. |
| ClientID string |
| // ClientSecret is currently only required if token_info endpoint also needs to be called with the generated GCP |
| // access token. When provided, STS will be called with additional basic authentication using client_id as username |
| // and client_secret as password. |
| ClientSecret string |
| // Token is the OAuth2.0 access token. Can be nil if refresh information is provided. |
| Token string |
| // Expiry is the optional expiration datetime of the OAuth 2.0 access token. |
| Expiry time.Time |
| // RevokeURL is the optional STS endpoint URL for revoking tokens. |
| RevokeURL string |
| // QuotaProjectID is the optional project ID used for quota and billing. This project may be different from the |
| // project used to create the credentials. |
| QuotaProjectID string |
| Scopes []string |
| } |
| |
| func (c *Config) canRefresh() bool { |
| return c.ClientID != "" && c.ClientSecret != "" && c.RefreshToken != "" && c.TokenURL != "" |
| } |
| |
| func (c *Config) TokenSource(ctx context.Context) (oauth2.TokenSource, error) { |
| var token oauth2.Token |
| if c.Token != "" && !c.Expiry.IsZero() { |
| token = oauth2.Token{ |
| AccessToken: c.Token, |
| Expiry: c.Expiry, |
| TokenType: "Bearer", |
| } |
| } |
| if !tokenValid(token) && !c.canRefresh() { |
| return nil, errors.New("oauth2/google: Token should be created with fields to make it valid (`token` and `expiry`), or fields to allow it to refresh (`refresh_token`, `token_url`, `client_id`, `client_secret`).") |
| } |
| |
| ts := tokenSource{ |
| ctx: ctx, |
| conf: c, |
| } |
| |
| return oauth2.ReuseTokenSource(&token, ts), nil |
| } |
| |
| type tokenSource struct { |
| ctx context.Context |
| conf *Config |
| } |
| |
| func (ts tokenSource) Token() (*oauth2.Token, error) { |
| conf := ts.conf |
| if !conf.canRefresh() { |
| return nil, errors.New("oauth2/google: The credentials do not contain the necessary fields need to refresh the access token. You must specify refresh_token, token_url, client_id, and client_secret.") |
| } |
| |
| clientAuth := stsexchange.ClientAuthentication{ |
| AuthStyle: oauth2.AuthStyleInHeader, |
| ClientID: conf.ClientID, |
| ClientSecret: conf.ClientSecret, |
| } |
| |
| stsResponse, err := stsexchange.RefreshAccessToken(ts.ctx, conf.TokenURL, conf.RefreshToken, clientAuth, nil) |
| if err != nil { |
| return nil, err |
| } |
| if stsResponse.ExpiresIn < 0 { |
| return nil, errors.New("oauth2/google: got invalid expiry from security token service") |
| } |
| |
| if stsResponse.RefreshToken != "" { |
| conf.RefreshToken = stsResponse.RefreshToken |
| } |
| |
| token := &oauth2.Token{ |
| AccessToken: stsResponse.AccessToken, |
| Expiry: now().Add(time.Duration(stsResponse.ExpiresIn) * time.Second), |
| TokenType: "Bearer", |
| } |
| return token, nil |
| } |