feat(client): OAuth2 Client credential support (#259)

* Initial implementation

* Added OAuth2 support to `client` config

* Revert "Initial implementation"

This reverts commit 7f2f3a603ae018b1cd1c6a282104f44cd9a1a1d1.

* Restore vendored clientcredentials

* configureOAuth2 is now a func (including tests)

* README update

* Use the same OAuth2Config in all related tests

* Cleanup & comments
This commit is contained in:
Andre Bindewald
2022-03-10 02:53:51 +01:00
committed by GitHub
parent fcf046cbe8
commit c4255e65bc
6 changed files with 280 additions and 7 deletions

View File

@ -1,9 +1,14 @@
package client
import (
"context"
"crypto/tls"
"errors"
"net/http"
"time"
"golang.org/x/oauth2"
"golang.org/x/oauth2/clientcredentials"
)
const (
@ -17,6 +22,10 @@ var (
IgnoreRedirect: false,
Timeout: defaultHTTPTimeout,
}
ErrInvalidClientOAuth2Config = errors.New(
"invalid OAuth2 configuration, all fields are required",
)
)
// GetDefaultConfig returns a copy of the default configuration
@ -36,14 +45,40 @@ type Config struct {
// Timeout for the client
Timeout time.Duration `yaml:"timeout"`
// OAuth2 configuration for the client
OAuth2Config *OAuth2Config `yaml:"oauth2,omitempty"`
httpClient *http.Client
}
// OAuth2Config is the configuration for the OAuth2 client credentials flow
type OAuth2Config struct {
TokenURL string `yaml:"token-url"` // e.g. https://dev-12345678.okta.com/token
ClientID string `yaml:"client-id"`
ClientSecret string `yaml:"client-secret"`
Scopes []string `yaml:"scopes"` // e.g. ["openid"]
}
// ValidateAndSetDefaults validates the client configuration and sets the default values if necessary
func (c *Config) ValidateAndSetDefaults() {
func (c *Config) ValidateAndSetDefaults() error {
if c.Timeout < time.Millisecond {
c.Timeout = 10 * time.Second
}
if c.HasOAuth2Config() && !c.OAuth2Config.isValid() {
return ErrInvalidClientOAuth2Config
}
return nil
}
// HasOAuth2Config returns true if the client has OAuth2 configuration parameters
func (c *Config) HasOAuth2Config() bool {
return c.OAuth2Config != nil
}
// isValid() returns true if the OAuth2 configuration is valid
func (c *OAuth2Config) isValid() bool {
return len(c.TokenURL) > 0 && len(c.ClientID) > 0 && len(c.ClientSecret) > 0 && len(c.Scopes) > 0
}
// GetHTTPClient return an HTTP client matching the Config's parameters.
@ -68,6 +103,22 @@ func (c *Config) getHTTPClient() *http.Client {
return nil
},
}
if c.HasOAuth2Config() {
c.httpClient = configureOAuth2(c.httpClient, *c.OAuth2Config)
}
}
return c.httpClient
}
// configureOAuth2 returns an HTTP client that will obtain and refresh tokens as necessary.
// The returned Client and its Transport should not be modified.
func configureOAuth2(httpClient *http.Client, c OAuth2Config) *http.Client {
oauth2cfg := clientcredentials.Config{
ClientID: c.ClientID,
ClientSecret: c.ClientSecret,
Scopes: c.Scopes,
TokenURL: c.TokenURL,
}
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, httpClient)
return oauth2cfg.Client(ctx)
}