Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement access_token auth method #83

Merged
merged 5 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,21 @@ Depending on the API you want to use, you may set the ``endpoint`` to:
This lookup mechanism makes it easy to overload credentials for a specific
project or user.

### Access Token

This authentication method is useful when short-lived credentials are necessary.
E.g. oauth2 [plugin](https://github.com/puppetlabs/vault-plugin-secrets-oauthapp)
for hashicorp vault can request an access token that would be used by OVH
0x416e746f6e marked this conversation as resolved.
Show resolved Hide resolved
terraform provider. Although this token, requested via data-source, would end up
stored in the terraform state-file, that would pose less risk since the token
0x416e746f6e marked this conversation as resolved.
Show resolved Hide resolved
validity would last for only 1 hour.

Other applications are of course also possible.

In order to use the access token with this wrapper either use
`ovh.NewAccessTokenClient` to create the client, or pass the token via
`OVH_ACCESS_TOKEN` environment variable to `ovh.NewDefaultClient`.

### Application Key/Application Secret

If you have completed successfully the __OAuth2__ part, you can continue to
Expand Down Expand Up @@ -354,9 +369,10 @@ client.Get("/xdsl/xdsl-yourservice", nil)

### Create a client

- Use ``ovh.NewDefaultClient()`` to create a client unsing endpoint and credentials from config files or environment
- Use ``ovh.NewDefaultClient()`` to create a client using endpoint and credentials from config files or environment
- Use ``ovh.NewEndpointClient()`` to create a client for a specific API and use credentials from config files or environment
- Use ``ovh.NewOAuth2Client()`` to have full control over their authentication, using OAuth2 authentication method
- Use ``ovh.NewAccessTokenClient()`` to have full control over their authentication, using token that was previously issued by auth/oauth2/token endpoint
- Use ``ovh.NewClient()`` to have full control over their authentication, using legacy authentication method

### Query
Expand Down
30 changes: 24 additions & 6 deletions ovh/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ func (c *Client) loadConfig(endpointName string) error {
endpointName = getConfigValue(cfg, "default", "endpoint", "ovh-eu")
}

if c.AccessToken == "" {
c.AccessToken = getConfigValue(cfg, endpointName, "access_token", "")
}

if c.AppKey == "" {
c.AppKey = getConfigValue(cfg, endpointName, "application_key", "")
}
Expand All @@ -125,19 +129,33 @@ func (c *Client) loadConfig(endpointName string) error {
c.ClientSecret = getConfigValue(cfg, endpointName, "client_secret", "")
}

configuredAuthMethods := []string{}
if c.AppKey != "" || c.AppSecret != "" || c.ConsumerKey != "" {
configuredAuthMethods = append(configuredAuthMethods, "application_key/application_secret")
}
if c.ClientID != "" || c.ClientSecret != "" {
configuredAuthMethods = append(configuredAuthMethods, "client_id/client_secret")
}
if c.AccessToken != "" {
configuredAuthMethods = append(configuredAuthMethods, "access_token")
}

if len(configuredAuthMethods) > 1 {
return fmt.Errorf("can't use multiple authentication methods: %s", strings.Join(configuredAuthMethods, ", "))
}
if len(configuredAuthMethods) == 0 {
return errors.New(
"missing authentication information, you need to provide one of the following: application_key/application_secret, client_id/client_secret, or access_token",
)
}

if (c.ClientID != "") != (c.ClientSecret != "") {
return errors.New("invalid oauth2 config, both client_id and client_secret must be given")
}
if (c.AppKey != "") != (c.AppSecret != "") {
return errors.New("invalid authentication config, both application_key and application_secret must be given")
}

if c.ClientID != "" && c.AppKey != "" {
return errors.New("can't use both application_key/application_secret and OAuth2 client_id/client_secret")
} else if c.ClientID == "" && c.AppKey == "" {
return errors.New("missing authentication information, you need to provide at least an application_key/application_secret or a client_id/client_secret")
}

// Load real endpoint URL by name. If endpoint contains a '/', consider it as a URL
if strings.Contains(endpointName, "/") {
c.endpoint = endpointName
Expand Down
4 changes: 2 additions & 2 deletions ovh/configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func TestConfigFromNonExistingFile(t *testing.T) {

client := Client{}
err := client.loadConfig("ovh-eu")
td.CmpString(t, err, `missing authentication information, you need to provide at least an application_key/application_secret or a client_id/client_secret`)
td.CmpString(t, err, `missing authentication information, you need to provide one of the following: application_key/application_secret, client_id/client_secret, or access_token`)
}

func TestConfigFromInvalidINIFile(t *testing.T) {
Expand Down Expand Up @@ -185,7 +185,7 @@ func TestConfigInvalidBoth(t *testing.T) {

client := Client{}
err := client.loadConfig("ovh-eu")
td.CmpString(t, err, "can't use both application_key/application_secret and OAuth2 client_id/client_secret")
td.CmpString(t, err, "can't use multiple authentication methods: application_key/application_secret, client_id/client_secret")
}

func TestConfigOAuth2Invalid(t *testing.T) {
Expand Down
19 changes: 19 additions & 0 deletions ovh/ovh.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ var (

// Client represents a client to call the OVH API
type Client struct {
// AccessToken is a short-lived access token that we got from auth/oauth2/token endpoint.
AccessToken string

// Self generated tokens. Create one by visiting
// https://eu.api.ovh.com/createApp/
// AppKey holds the Application key
Expand Down Expand Up @@ -141,6 +144,20 @@ func NewOAuth2Client(endpoint, clientID, clientSecret string) (*Client, error) {
return &client, nil
}

func NewAccessTokenClient(endpoint, accessToken string) (*Client, error) {
client := Client{
AccessToken: accessToken,
Client: &http.Client{},
Timeout: DefaultTimeout,
}

// Get and check the configuration
if err := client.loadConfig(endpoint); err != nil {
return nil, err
}
return &client, nil
}

func (c *Client) Endpoint() string {
return c.endpoint
}
Expand Down Expand Up @@ -351,6 +368,8 @@ func (c *Client) NewRequest(method, path string, reqBody interface{}, needAuth b
}

req.Header.Set("Authorization", "Bearer "+token.AccessToken)
} else if c.AccessToken != "" {
req.Header.Set("Authorization", "Bearer "+c.AccessToken)
}
}

Expand Down
20 changes: 20 additions & 0 deletions ovh/ovh_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,26 @@ func TestConstructorsOAuth2(t *testing.T) {
}))
}

func TestConstructorsAccessToken(t *testing.T) {
assert, require := td.AssertRequire(t)

// Error: missing Endpoint
client, err := NewAccessTokenClient("", "aaaaaaaa")
assert.Nil(client)
assert.String(err, `unknown endpoint '', consider checking 'Endpoints' list or using an URL`)

// Next: success cases
expected := td.Struct(&Client{
AccessToken: "aaaaaaaa",
endpoint: "https://eu.api.ovh.com/1.0",
})

// Nominal: full constructor
client, err = NewAccessTokenClient("ovh-eu", "aaaaaaaa")
require.CmpNoError(err)
assert.Cmp(client, expected)
}

func (ms *MockSuite) TestVersionInURL(assert, require *td.T) {
// Signature checking mocks
httpmock.RegisterResponder("GET", "https://eu.api.ovh.com/1.0/call", func(req *http.Request) (*http.Response, error) {
Expand Down
Loading