diff --git a/core/container_authenticator.go b/core/container_authenticator.go index 35e72ce..2d7c3f5 100644 --- a/core/container_authenticator.go +++ b/core/container_authenticator.go @@ -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. @@ -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 @@ -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) { @@ -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) diff --git a/core/container_authenticator_test.go b/core/container_authenticator_test.go index 1a66997..f93ba10 100644 --- a/core/container_authenticator_test.go +++ b/core/container_authenticator_test.go @@ -20,6 +20,7 @@ import ( "fmt" "net/http" "net/http/httptest" + "strings" "testing" "time" @@ -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")) diff --git a/core/cp4d_authenticator.go b/core/cp4d_authenticator.go index 1a2ba76..8f9a811 100644 --- a/core/cp4d_authenticator.go +++ b/core/cp4d_authenticator.go @@ -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. @@ -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 @@ -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. // @@ -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) diff --git a/core/cp4d_authenticator_test.go b/core/cp4d_authenticator_test.go index cb98f36..de6bcfc 100644 --- a/core/cp4d_authenticator_test.go +++ b/core/cp4d_authenticator_test.go @@ -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. @@ -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) diff --git a/core/iam_authenticator.go b/core/iam_authenticator.go index cb64f62..da9feb6 100644 --- a/core/iam_authenticator.go +++ b/core/iam_authenticator.go @@ -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 @@ -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, @@ -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 != "" { diff --git a/core/iam_authenticator_test.go b/core/iam_authenticator_test.go index e88b10a..d52360f 100644 --- a/core/iam_authenticator_test.go +++ b/core/iam_authenticator_test.go @@ -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() diff --git a/core/mcsp_authenticator.go b/core/mcsp_authenticator.go index f79da05..3bd2a2f 100644 --- a/core/mcsp_authenticator.go +++ b/core/mcsp_authenticator.go @@ -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. @@ -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 @@ -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 { @@ -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) diff --git a/core/mcsp_authenticator_test.go b/core/mcsp_authenticator_test.go index 3b9b236..7df1a8d 100644 --- a/core/mcsp_authenticator_test.go +++ b/core/mcsp_authenticator_test.go @@ -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. @@ -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() diff --git a/core/vpc_instance_authenticator.go b/core/vpc_instance_authenticator.go index 10d4c53..562318b 100644 --- a/core/vpc_instance_authenticator.go +++ b/core/vpc_instance_authenticator.go @@ -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. @@ -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 @@ -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() { @@ -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. diff --git a/core/vpc_instance_authenticator_test.go b/core/vpc_instance_authenticator_test.go index 4fd0130..632c0c8 100644 --- a/core/vpc_instance_authenticator_test.go +++ b/core/vpc_instance_authenticator_test.go @@ -21,6 +21,7 @@ import ( "fmt" "net/http" "net/http/httptest" + "strings" "time" "github.com/go-openapi/strfmt" @@ -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).