Skip to content

Commit

Permalink
feat: send user-agent header with auth token requests (#216)
Browse files Browse the repository at this point in the history
This commit updates our various request-based authenticators
so that the User-Agent header is included with each outbound
token request. The value of the User-Agent header will be
of the form "ibm-go-sdk-core/<authenticator-type>-<core-version> <os-info>".

Signed-off-by: Phil Adams <phil_adams@us.ibm.com>
  • Loading branch information
padamstx committed Apr 17, 2024
1 parent e61658f commit 90f0ba5
Show file tree
Hide file tree
Showing 10 changed files with 84 additions and 7 deletions.
15 changes: 14 additions & 1 deletion core/container_authenticator.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package core

// (C) Copyright IBM Corp. 2021, 2023..
// (C) Copyright IBM Corp. 2021, 2024.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -86,6 +86,10 @@ type ContainerAuthenticator struct {
Client *http.Client
clientInit sync.Once

// The User-Agent header value to be included with each token request.
userAgent string
userAgentInit sync.Once

// The cached IAM access token and its expiration time.
tokenData *iamTokenData

Expand Down Expand Up @@ -199,6 +203,14 @@ func (authenticator *ContainerAuthenticator) client() *http.Client {
return authenticator.Client
}

// getUserAgent returns the User-Agent header value to be included in each token request invoked by the authenticator.
func (authenticator *ContainerAuthenticator) getUserAgent() string {
authenticator.userAgentInit.Do(func() {
authenticator.userAgent = fmt.Sprintf("%s/%s-%s %s", sdkName, "container-authenticator", __VERSION__, SystemInfo())
})
return authenticator.userAgent
}

// newContainerAuthenticatorFromMap constructs a new ContainerAuthenticator instance from a map containing
// configuration properties.
func newContainerAuthenticatorFromMap(properties map[string]string) (authenticator *ContainerAuthenticator, err error) {
Expand Down Expand Up @@ -392,6 +404,7 @@ func (authenticator *ContainerAuthenticator) RequestToken() (*IamTokenServerResp

builder.AddHeader(CONTENT_TYPE, FORM_URL_ENCODED_HEADER)
builder.AddHeader(Accept, APPLICATION_JSON)
builder.AddHeader(headerNameUserAgent, authenticator.getUserAgent())
builder.AddFormData("grant_type", "", "", iamGrantTypeCRToken) // #nosec G101
builder.AddFormData("cr_token", "", "", crToken)

Expand Down
3 changes: 3 additions & 0 deletions core/container_authenticator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"fmt"
"net/http"
"net/http/httptest"
"strings"

"testing"
"time"
Expand Down Expand Up @@ -257,6 +258,8 @@ func startMockIAMServer(t *testing.T) *httptest.Server {
// then validate it a bit and then send back a good response.
assert.Equal(t, APPLICATION_JSON, req.Header.Get("Accept"))
assert.Equal(t, FORM_URL_ENCODED_HEADER, req.Header.Get("Content-Type"))
assert.True(t, strings.HasPrefix(req.Header.Get(headerNameUserAgent),
fmt.Sprintf("%s/%s", sdkName, "container-authenticator")))
assert.Equal(t, containerAuthTestCRToken1, req.FormValue("cr_token"))
assert.Equal(t, iamGrantTypeCRToken, req.FormValue("grant_type"))

Expand Down
17 changes: 15 additions & 2 deletions core/cp4d_authenticator.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package core

// (C) Copyright IBM Corp. 2019, 2021.
// (C) Copyright IBM Corp. 2019, 2024.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -57,6 +57,10 @@ type CloudPakForDataAuthenticator struct {
Client *http.Client
clientInit sync.Once

// The User-Agent header value to be included with each token request.
userAgent string
userAgentInit sync.Once

// The cached token and expiration time.
tokenData *cp4dTokenData

Expand Down Expand Up @@ -182,6 +186,14 @@ func (authenticator *CloudPakForDataAuthenticator) client() *http.Client {
return authenticator.Client
}

// getUserAgent returns the User-Agent header value to be included in each token request invoked by the authenticator.
func (authenticator *CloudPakForDataAuthenticator) getUserAgent() string {
authenticator.userAgentInit.Do(func() {
authenticator.userAgent = fmt.Sprintf("%s/%s-%s %s", sdkName, "cp4d-authenticator", __VERSION__, SystemInfo())
})
return authenticator.userAgent
}

// Authenticate adds the bearer token (obtained from the token server) to the
// specified request.
//
Expand Down Expand Up @@ -304,8 +316,9 @@ func (authenticator *CloudPakForDataAuthenticator) requestToken() (tokenResponse
builder.AddHeader(headerName, headerValue)
}

// Add the Content-Type header.
// Add the Content-Type and User-Agent headers.
builder.AddHeader("Content-Type", "application/json")
builder.AddHeader(headerNameUserAgent, authenticator.getUserAgent())

// Add the request body to request.
_, err = builder.SetBodyContentJSON(body)
Expand Down
4 changes: 3 additions & 1 deletion core/cp4d_authenticator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

package core

// (C) Copyright IBM Corp. 2019, 2021.
// (C) Copyright IBM Corp. 2019, 2024.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -585,6 +585,8 @@ func TestCp4dUserHeaders(t *testing.T) {
assert.Equal(t, "Value1", r.Header.Get("Header1"))
assert.Equal(t, "Value2", r.Header.Get("Header2"))
assert.Equal(t, "cp4d.cloud.ibm.com", r.Host)
assert.True(t, strings.HasPrefix(r.Header.Get(headerNameUserAgent),
fmt.Sprintf("%s/%s", sdkName, "cp4d-authenticator")))

w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `{ "_messageCode_":"200", "message":"success", "token":"%s"}`, cp4dUsernameApikey1)
Expand Down
13 changes: 13 additions & 0 deletions core/iam_authenticator.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ type IamAuthenticator struct {
Client *http.Client
clientInit sync.Once

// The User-Agent header value to be included with each token request.
userAgent string
userAgentInit sync.Once

// The cached token and expiration time.
tokenData *iamTokenData

Expand Down Expand Up @@ -190,6 +194,14 @@ func (authenticator *IamAuthenticator) client() *http.Client {
return authenticator.Client
}

// getUserAgent returns the User-Agent header value to be included in each token request invoked by the authenticator.
func (authenticator *IamAuthenticator) getUserAgent() string {
authenticator.userAgentInit.Do(func() {
authenticator.userAgent = fmt.Sprintf("%s/%s-%s %s", sdkName, "iam-authenticator", __VERSION__, SystemInfo())
})
return authenticator.userAgent
}

// NewIamAuthenticator constructs a new IamAuthenticator instance.
// Deprecated - use the IamAuthenticatorBuilder instead.
func NewIamAuthenticator(apiKey string, url string, clientId string, clientSecret string,
Expand Down Expand Up @@ -413,6 +425,7 @@ func (authenticator *IamAuthenticator) RequestToken() (*IamTokenServerResponse,

builder.AddHeader(CONTENT_TYPE, "application/x-www-form-urlencoded")
builder.AddHeader(Accept, APPLICATION_JSON)
builder.AddHeader(headerNameUserAgent, authenticator.getUserAgent())
builder.AddFormData("response_type", "", "", "cloud_iam")

if authenticator.ApiKey != "" {
Expand Down
2 changes: 2 additions & 0 deletions core/iam_authenticator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -965,6 +965,8 @@ func TestIamUserHeaders(t *testing.T) {
assert.False(t, ok)
assert.Equal(t, "Value1", r.Header.Get("Header1"))
assert.Equal(t, "Value2", r.Header.Get("Header2"))
assert.True(t, strings.HasPrefix(r.Header.Get(headerNameUserAgent),
fmt.Sprintf("%s/%s", sdkName, "iam-authenticator")))
assert.Equal(t, "iam.cloud.ibm.com", r.Host)
}))
defer server.Close()
Expand Down
15 changes: 14 additions & 1 deletion core/mcsp_authenticator.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package core

// (C) Copyright IBM Corp. 2023.
// (C) Copyright IBM Corp. 2023, 2024.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -49,6 +49,10 @@ type MCSPAuthenticator struct {
Client *http.Client
clientInit sync.Once

// The User-Agent header value to be included with each token request.
userAgent string
userAgentInit sync.Once

// The cached token and expiration time.
tokenData *mcspTokenData

Expand Down Expand Up @@ -136,6 +140,14 @@ func (authenticator *MCSPAuthenticator) client() *http.Client {
return authenticator.Client
}

// getUserAgent returns the User-Agent header value to be included in each token request invoked by the authenticator.
func (authenticator *MCSPAuthenticator) getUserAgent() string {
authenticator.userAgentInit.Do(func() {
authenticator.userAgent = fmt.Sprintf("%s/%s-%s %s", sdkName, "mcsp-authenticator", __VERSION__, SystemInfo())
})
return authenticator.userAgent
}

// newMCSPAuthenticatorFromMap constructs a new MCSPAuthenticator instance from a map.
func newMCSPAuthenticatorFromMap(properties map[string]string) (authenticator *MCSPAuthenticator, err error) {
if properties == nil {
Expand Down Expand Up @@ -280,6 +292,7 @@ func (authenticator *MCSPAuthenticator) RequestToken() (*MCSPTokenServerResponse

builder.AddHeader(CONTENT_TYPE, APPLICATION_JSON)
builder.AddHeader(Accept, APPLICATION_JSON)
builder.AddHeader(headerNameUserAgent, authenticator.getUserAgent())
requestBody := fmt.Sprintf(`{"apikey":"%s"}`, authenticator.ApiKey)
_, _ = builder.SetBodyContentString(requestBody)

Expand Down
4 changes: 3 additions & 1 deletion core/mcsp_authenticator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

package core

// (C) Copyright IBM Corp. 2023.
// (C) Copyright IBM Corp. 2023, 2024.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -467,6 +467,8 @@ func TestMCSPUserHeaders(t *testing.T) {
fmt.Fprintf(w, `{"token":"%s","token_type":"jwt","expires_in":7200}`, mcspAuthTestAccessToken1)
assert.Equal(t, "Value1", r.Header.Get("Header1"))
assert.Equal(t, "Value2", r.Header.Get("Header2"))
assert.True(t, strings.HasPrefix(r.Header.Get(headerNameUserAgent),
fmt.Sprintf("%s/%s", sdkName, "mcsp-authenticator")))
assert.Equal(t, "mcsp.cloud.ibm.com", r.Host)
}))
defer server.Close()
Expand Down
15 changes: 14 additions & 1 deletion core/vpc_instance_authenticator.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package core

// (C) Copyright IBM Corp. 2021, 2023.
// (C) Copyright IBM Corp. 2021, 2024.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -58,6 +58,10 @@ type VpcInstanceAuthenticator struct {
Client *http.Client
clientInit sync.Once

// The User-Agent header value to be included with each token request.
userAgent string
userAgentInit sync.Once

// The cached IAM access token and its expiration time.
tokenData *iamTokenData

Expand Down Expand Up @@ -133,6 +137,14 @@ func (authenticator *VpcInstanceAuthenticator) client() *http.Client {
return authenticator.Client
}

// getUserAgent returns the User-Agent header value to be included in each token request invoked by the authenticator.
func (authenticator *VpcInstanceAuthenticator) getUserAgent() string {
authenticator.userAgentInit.Do(func() {
authenticator.userAgent = fmt.Sprintf("%s/%s-%s %s", sdkName, "vpc-instance-authenticator", __VERSION__, SystemInfo())
})
return authenticator.userAgent
}

// url returns the authenticator's URL property after potentially initializing it.
func (authenticator *VpcInstanceAuthenticator) url() string {
authenticator.urlInit.Do(func() {
Expand Down Expand Up @@ -332,6 +344,7 @@ func (authenticator *VpcInstanceAuthenticator) retrieveIamAccessToken(
builder.AddQuery("version", vpcauthMetadataServiceVersion)
builder.AddHeader(CONTENT_TYPE, APPLICATION_JSON)
builder.AddHeader(Accept, APPLICATION_JSON)
builder.AddHeader(headerNameUserAgent, authenticator.getUserAgent())
builder.AddHeader("Authorization", "Bearer "+instanceIdentityToken)

// Next, construct the optional request body to specify the linked IAM profile.
Expand Down
3 changes: 3 additions & 0 deletions core/vpc_instance_authenticator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
"net/http"
"net/http/httptest"
"strings"
"time"

"github.com/go-openapi/strfmt"
Expand Down Expand Up @@ -293,6 +294,8 @@ func startMockVPCServer(t *testing.T, scenario string) *httptest.Server {
assert.NotEmpty(t, req.URL.Query().Get("version"))
assert.Equal(t, APPLICATION_JSON, req.Header.Get("Accept"))
assert.Equal(t, APPLICATION_JSON, req.Header.Get("Content-Type"))
assert.True(t, strings.HasPrefix(req.Header.Get(headerNameUserAgent),
fmt.Sprintf("%s/%s", sdkName, "vpc-instance-authenticator")))
assert.Equal(t, expectedAuthorizationHeader, req.Header.Get("Authorization"))

// Models a trusted profile (includes both CRN and ID fields).
Expand Down

0 comments on commit 90f0ba5

Please sign in to comment.