From 1d9ea0c8e4ac72a6e037dfa3c7abaee343738393 Mon Sep 17 00:00:00 2001 From: Patrick Jones Date: Thu, 17 Jun 2021 14:58:26 -0700 Subject: [PATCH] downscope: refactor main functionality into a method on a tokenSource, update examples accordingly, and test for maximum boundary rule violations --- google/downscope/downscoping.go | 33 ++++++++++++++++-------- google/downscope/example_test.go | 43 +++----------------------------- 2 files changed, 26 insertions(+), 50 deletions(-) diff --git a/google/downscope/downscoping.go b/google/downscope/downscoping.go index 70e0b21..65f6098 100644 --- a/google/downscope/downscoping.go +++ b/google/downscope/downscoping.go @@ -78,18 +78,31 @@ type DownscopingConfig struct { // downscoped Token. One or more AccessBoundaryRules are required to // define permissions for the new downscoped token. Each one defines an // access (or set of accesses) that the new token has to a given resource. + // There can be a maximum of 10 AccessBoundaryRules. Rules []AccessBoundaryRule } +// A DownscopingTokenSource is used to retrieve a downscoped token with restricted +// permissions compared to the root Token that is used to generate it. +type DownscopingTokenSource struct { + // Ctx is the context used to query the API to retrieve a downscoped Token. + Ctx context.Context + // Config holds the information necessary to generate a downscoped Token. + Config DownscopingConfig +} + // downscopedTokenWithEndpoint is a helper function used for unit testing // purposes, as it allows us to pass in a locally mocked endpoint. -func downscopedTokenWithEndpoint(ctx context.Context, config DownscopingConfig, endpoint string) (oauth2.TokenSource, error) { +func downscopedTokenWithEndpoint(ctx context.Context, config DownscopingConfig, endpoint string) (*oauth2.Token, error) { if config.RootSource == nil { return nil, fmt.Errorf("downscope: rootTokenSource cannot be nil") } if len(config.Rules) == 0 { return nil, fmt.Errorf("downscope: length of AccessBoundaryRules must be at least 1") } + if len(config.Rules) > 10 { + return nil, fmt.Errorf("downscope: length of AccessBoundaryRules may not be greater than 10") + } for _, val := range config.Rules { if val.AvailableResource == "" { return nil, fmt.Errorf("downscope: all rules must have a nonempty AvailableResource: %+v", val) @@ -160,16 +173,14 @@ func downscopedTokenWithEndpoint(ctx context.Context, config DownscopingConfig, TokenType: tresp.TokenType, Expiry: expiryTime, } - return oauth2.StaticTokenSource(newToken), nil + return newToken, nil } -// NewTokenSource takes a root TokenSource and returns a downscoped TokenSource -// with a subset of the permissions held by the root source. The -// CredentialAccessBoundary in the config defines the permissions held -// by the new TokenSource. Do note that the returned TokenSource is -// an oauth2.StaticTokenSource. If you wish to refresh this token automatically, -// then initialize a locally defined TokenSource struct with the Token held -// by the StaticTokenSource and wrap that TokenSource in an oauth2.ReuseTokenSource. -func NewTokenSource(ctx context.Context, config DownscopingConfig) (oauth2.TokenSource, error) { - return downscopedTokenWithEndpoint(ctx, config, identityBindingEndpoint) +// Token() uses a DownscopingTokenSource to generate an oauth2 Token. +// Do note that the returned TokenSource is an oauth2.StaticTokenSource. If you wish +// to refresh this token automatically, then initialize a locally defined +// TokenSource struct with the Token held by the StaticTokenSource and wrap +// that TokenSource in an oauth2.ReuseTokenSource. +func (dts DownscopingTokenSource) Token() (*oauth2.Token, error) { + return downscopedTokenWithEndpoint(dts.Ctx, dts.Config, identityBindingEndpoint) } diff --git a/google/downscope/example_test.go b/google/downscope/example_test.go index 4112234..35d3d83 100644 --- a/google/downscope/example_test.go +++ b/google/downscope/example_test.go @@ -2,7 +2,6 @@ package downscope_test import ( "context" - "log" "golang.org/x/oauth2" "golang.org/x/oauth2/google/downscope" @@ -19,49 +18,15 @@ func ExampleNewTokenSource() { } var rootSource oauth2.TokenSource - // This Source can be initialized using Application Default Credentials as follows: + // This Source can be initialized in multiple ways; the following example uses + // Application Default Credentials. // rootSource, err := google.DefaultTokenSource(ctx, "https://www.googleapis.com/auth/cloud-platform") - myTokenSource, err := downscope.NewTokenSource(ctx, downscope.DownscopingConfig{RootSource: rootSource, Rules: accessBoundary}) - //myTokenSource, err := NewSource(rootSource, myBoundary) - if err != nil { - log.Fatalf("failed to generate downscoped token source: %v", err) - } - _ = myTokenSource + dts := downscope.DownscopingTokenSource{ctx, downscope.DownscopingConfig{RootSource: rootSource, Rules: accessBoundary}} + _ = dts // You can now use the token held in myTokenSource to make // Google Cloud Storage calls, as follows: // storageClient, err := storage.NewClient(ctx, option.WithTokenSource(myTokenSource)) } - -type localTokenSource struct { - tokenBrokerURL string - tokenSourceForBroker oauth2.TokenSource -} - -func (lts localTokenSource) Token() (*oauth2.Token, error) { - // Make a call to a remote token broker, which runs downscope.NewTokenSource() - // to generate a downscoped version of a token it holds. Returns said token. - var tok oauth2.Token - return &tok, nil -} - -// ExampleRefreshableToken provices a sample of how a token consumer would -// construct a refreshable token by wrapping a method that requests a -// downscoped token from a token broker in an oauth2.ReuseTokenSource -func ExampleNewTokenSource_refresh() { - var myCredentials oauth2.TokenSource - // This Source contains the credentials that the token consumer uses to - // authenticate itself to the token broker from which it is requesting - // a downscoped token. - myTokenSource := localTokenSource{ - tokenBrokerURL: "www.foo.bar", - tokenSourceForBroker: myCredentials, - } - - downscopedToken := oauth2.ReuseTokenSource(nil, myTokenSource) - // downscopedToken can now be used as a refreshable token for Google Cloud Storage calls - // storageClient, err := storage.NewClient(ctx, option.WithTokenSource(myTokenSource)) - _ = downscopedToken -}