idun/vendor/github.com/aws/aws-sdk-go/aws/credentials/ssocreds/token_provider.go

149 lines
5.1 KiB
Go

package ssocreds
import (
"fmt"
"os"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/auth/bearer"
"github.com/aws/aws-sdk-go/service/ssooidc"
)
// CreateTokenAPIClient provides the interface for the SSOTokenProvider's API
// client for calling CreateToken operation to refresh the SSO token.
type CreateTokenAPIClient interface {
CreateToken(input *ssooidc.CreateTokenInput) (*ssooidc.CreateTokenOutput, error)
}
// SSOTokenProviderOptions provides the options for configuring the
// SSOTokenProvider.
type SSOTokenProviderOptions struct {
// Client that can be overridden
Client CreateTokenAPIClient
// The path the file containing the cached SSO token will be read from.
// Initialized the NewSSOTokenProvider's cachedTokenFilepath parameter.
CachedTokenFilepath string
}
// SSOTokenProvider provides a utility for refreshing SSO AccessTokens for
// Bearer Authentication. The SSOTokenProvider can only be used to refresh
// already cached SSO Tokens. This utility cannot perform the initial SSO
// create token.
//
// The initial SSO create token should be preformed with the AWS CLI before the
// Go application using the SSOTokenProvider will need to retrieve the SSO
// token. If the AWS CLI has not created the token cache file, this provider
// will return an error when attempting to retrieve the cached token.
//
// This provider will attempt to refresh the cached SSO token periodically if
// needed when RetrieveBearerToken is called.
//
// A utility such as the AWS CLI must be used to initially create the SSO
// session and cached token file.
// https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html
type SSOTokenProvider struct {
options SSOTokenProviderOptions
}
// NewSSOTokenProvider returns an initialized SSOTokenProvider that will
// periodically refresh the SSO token cached stored in the cachedTokenFilepath.
// The cachedTokenFilepath file's content will be rewritten by the token
// provider when the token is refreshed.
//
// The client must be configured for the AWS region the SSO token was created for.
func NewSSOTokenProvider(client CreateTokenAPIClient, cachedTokenFilepath string, optFns ...func(o *SSOTokenProviderOptions)) *SSOTokenProvider {
options := SSOTokenProviderOptions{
Client: client,
CachedTokenFilepath: cachedTokenFilepath,
}
for _, fn := range optFns {
fn(&options)
}
provider := &SSOTokenProvider{
options: options,
}
return provider
}
// RetrieveBearerToken returns the SSO token stored in the cachedTokenFilepath
// the SSOTokenProvider was created with. If the token has expired
// RetrieveBearerToken will attempt to refresh it. If the token cannot be
// refreshed or is not present an error will be returned.
//
// A utility such as the AWS CLI must be used to initially create the SSO
// session and cached token file. https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html
func (p *SSOTokenProvider) RetrieveBearerToken(ctx aws.Context) (bearer.Token, error) {
cachedToken, err := loadCachedToken(p.options.CachedTokenFilepath)
if err != nil {
return bearer.Token{}, err
}
if cachedToken.ExpiresAt != nil && nowTime().After(time.Time(*cachedToken.ExpiresAt)) {
cachedToken, err = p.refreshToken(cachedToken)
if err != nil {
return bearer.Token{}, fmt.Errorf("refresh cached SSO token failed, %v", err)
}
}
expiresAt := toTime((*time.Time)(cachedToken.ExpiresAt))
return bearer.Token{
Value: cachedToken.AccessToken,
CanExpire: !expiresAt.IsZero(),
Expires: expiresAt,
}, nil
}
func (p *SSOTokenProvider) refreshToken(token cachedToken) (cachedToken, error) {
if token.ClientSecret == "" || token.ClientID == "" || token.RefreshToken == "" {
return cachedToken{}, fmt.Errorf("cached SSO token is expired, or not present, and cannot be refreshed")
}
createResult, err := p.options.Client.CreateToken(&ssooidc.CreateTokenInput{
ClientId: &token.ClientID,
ClientSecret: &token.ClientSecret,
RefreshToken: &token.RefreshToken,
GrantType: aws.String("refresh_token"),
})
if err != nil {
return cachedToken{}, fmt.Errorf("unable to refresh SSO token, %v", err)
}
if createResult.ExpiresIn == nil {
return cachedToken{}, fmt.Errorf("missing required field ExpiresIn")
}
if createResult.AccessToken == nil {
return cachedToken{}, fmt.Errorf("missing required field AccessToken")
}
if createResult.RefreshToken == nil {
return cachedToken{}, fmt.Errorf("missing required field RefreshToken")
}
expiresAt := nowTime().Add(time.Duration(*createResult.ExpiresIn) * time.Second)
token.AccessToken = *createResult.AccessToken
token.ExpiresAt = (*rfc3339)(&expiresAt)
token.RefreshToken = *createResult.RefreshToken
fileInfo, err := os.Stat(p.options.CachedTokenFilepath)
if err != nil {
return cachedToken{}, fmt.Errorf("failed to stat cached SSO token file %v", err)
}
if err = storeCachedToken(p.options.CachedTokenFilepath, token, fileInfo.Mode()); err != nil {
return cachedToken{}, fmt.Errorf("unable to cache refreshed SSO token, %v", err)
}
return token, nil
}
func toTime(p *time.Time) (v time.Time) {
if p == nil {
return v
}
return *p
}