Skip to content

Commit

Permalink
chore: Add X-Goog-Api-Client metric header to outgoing requests
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanedey committed Nov 19, 2024
1 parent 4d55c62 commit 084440c
Show file tree
Hide file tree
Showing 16 changed files with 99 additions and 39 deletions.
19 changes: 19 additions & 0 deletions appcheck/appcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package appcheck
import (
"context"
"errors"
"net/http"
"strings"
"time"

Expand Down Expand Up @@ -77,6 +78,7 @@ func NewClient(ctx context.Context, conf *internal.AppCheckConfig) (*Client, err
jwks, err := keyfunc.Get(JWKSUrl, keyfunc.Options{
Ctx: ctx,
RefreshInterval: 6 * time.Hour,
RequestFactory: makeRequestFactory(conf),
})
if err != nil {
return nil, err
Expand All @@ -88,6 +90,23 @@ func NewClient(ctx context.Context, conf *internal.AppCheckConfig) (*Client, err
}, nil
}

func makeRequestFactory(conf *internal.AppCheckConfig) func(ctx context.Context, url string) (*http.Request, error) {
opts := []internal.HTTPOption{
internal.WithHeader("x-goog-api-client", internal.GetMetricsHeader(conf.Version)),
}

return func(ctx context.Context, url string) (*http.Request, error) {
hr, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}
for _, o := range opts {
o(hr)
}
return hr, nil
}
}

// VerifyToken verifies the given App Check token.
//
// VerifyToken considers an App Check token string to be valid if all the following conditions are met:
Expand Down
37 changes: 21 additions & 16 deletions appcheck/appcheck_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,14 @@ import (
"github.com/google/go-cmp/cmp"
)

var testAppCheckConfig = &internal.AppCheckConfig{
ProjectID: "project_id",
Version: "test-version",
}

func TestVerifyTokenHasValidClaims(t *testing.T) {
ts, err := setupFakeJWKS()
var tr http.Request
ts, err := setupFakeJWKS(&tr)
if err != nil {
t.Fatalf("Error setting up fake JWKS server: %v", err)
}
Expand All @@ -30,15 +36,17 @@ func TestVerifyTokenHasValidClaims(t *testing.T) {
}

JWKSUrl = ts.URL
conf := &internal.AppCheckConfig{
ProjectID: "project_id",
}

client, err := NewClient(context.Background(), conf)
client, err := NewClient(context.Background(), testAppCheckConfig)
if err != nil {
t.Errorf("Error creating NewClient: %v", err)
}

xGoogAPIClientHeader := internal.GetMetricsHeader(testAppCheckConfig.Version)
if h := tr.Header.Get("x-goog-api-client"); h != xGoogAPIClientHeader {
t.Errorf("x-goog-api-client header = %q; want = %q", h, xGoogAPIClientHeader)
}

type appCheckClaims struct {
Aud []string `json:"aud"`
jwt.RegisteredClaims
Expand Down Expand Up @@ -169,18 +177,16 @@ func TestVerifyTokenHasValidClaims(t *testing.T) {
}

func TestVerifyTokenMustExist(t *testing.T) {
ts, err := setupFakeJWKS()
var tr http.Request
ts, err := setupFakeJWKS(&tr)
if err != nil {
t.Fatalf("Error setting up fake JWK server: %v", err)
}
defer ts.Close()

JWKSUrl = ts.URL
conf := &internal.AppCheckConfig{
ProjectID: "project_id",
}

client, err := NewClient(context.Background(), conf)
client, err := NewClient(context.Background(), testAppCheckConfig)
if err != nil {
t.Errorf("Error creating NewClient: %v", err)
}
Expand All @@ -197,7 +203,8 @@ func TestVerifyTokenMustExist(t *testing.T) {
}

func TestVerifyTokenNotExpired(t *testing.T) {
ts, err := setupFakeJWKS()
var tr http.Request
ts, err := setupFakeJWKS(&tr)
if err != nil {
t.Fatalf("Error setting up fake JWKS server: %v", err)
}
Expand All @@ -209,11 +216,8 @@ func TestVerifyTokenNotExpired(t *testing.T) {
}

JWKSUrl = ts.URL
conf := &internal.AppCheckConfig{
ProjectID: "project_id",
}

client, err := NewClient(context.Background(), conf)
client, err := NewClient(context.Background(), testAppCheckConfig)
if err != nil {
t.Errorf("Error creating NewClient: %v", err)
}
Expand Down Expand Up @@ -264,12 +268,13 @@ func TestVerifyTokenNotExpired(t *testing.T) {
}
}

func setupFakeJWKS() (*httptest.Server, error) {
func setupFakeJWKS(tr *http.Request) (*httptest.Server, error) {
jwks, err := os.ReadFile("../testdata/mock.jwks.json")
if err != nil {
return nil, err
}
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
*tr = *r
w.Write(jwks)
}))
return ts, nil
Expand Down
4 changes: 1 addition & 3 deletions auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"errors"
"fmt"
"os"
"runtime"
"strings"
"time"

Expand Down Expand Up @@ -134,12 +133,11 @@ func NewClient(ctx context.Context, conf *internal.AuthConfig) (*Client, error)
return nil, err
}

goVersion := strings.TrimPrefix(runtime.Version(), "go")
hc := internal.WithDefaultRetryConfig(transport)
hc.CreateErrFn = handleHTTPError
hc.Opts = []internal.HTTPOption{
internal.WithHeader("X-Client-Version", fmt.Sprintf("Go/Admin/%s", conf.Version)),
internal.WithHeader("x-goog-api-client", fmt.Sprintf("gl-go/%s fire-admin/%s", goVersion, conf.Version)),
internal.WithHeader("x-goog-api-client", internal.GetMetricsHeader(conf.Version)),
}

baseURL := defaultAuthURL
Expand Down
9 changes: 3 additions & 6 deletions auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"log"
"net/http"
"os"
"runtime"
"strings"
"syscall"
"testing"
Expand Down Expand Up @@ -1452,11 +1451,9 @@ func checkBaseClient(client *Client, wantProjectID string) error {
return fmt.Errorf("version = %q; want = %q", version, wantVersion)
}

goVersion := strings.TrimPrefix(runtime.Version(), "go")
xGoogAPIClientHeader := req.Header.Get("x-goog-api-client")
wantXGoogAPIClientHeader := fmt.Sprintf("gl-go/%s fire-admin/%s", goVersion, testVersion)
if xGoogAPIClientHeader != wantXGoogAPIClientHeader {
return fmt.Errorf("x-goog-api-client header = %q; want = %q", xGoogAPIClientHeader, wantXGoogAPIClientHeader)
xGoogAPIClientHeader := internal.GetMetricsHeader(testVersion)
if h := req.Header.Get("x-goog-api-client"); h != xGoogAPIClientHeader {
return fmt.Errorf("x-goog-api-client header = %q; want = %q", h, xGoogAPIClientHeader)
}

return nil
Expand Down
3 changes: 3 additions & 0 deletions auth/token_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,9 @@ type iamSigner struct {

func newIAMSigner(ctx context.Context, config *internal.AuthConfig) (*iamSigner, error) {
hc, _, err := internal.NewHTTPClient(ctx, config.Opts...)
hc.Opts = []internal.HTTPOption{
internal.WithHeader("x-goog-api-client", internal.GetMetricsHeader(config.Version)),
}
if err != nil {
return nil, err
}
Expand Down
13 changes: 11 additions & 2 deletions auth/token_generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ func TestIAMSigner(t *testing.T) {
conf := &internal.AuthConfig{
Opts: optsWithTokenSource,
ServiceAccountID: "test-service-account",
Version: testVersion,
}
signer, err := newIAMSigner(ctx, conf)
if err != nil {
Expand Down Expand Up @@ -155,6 +156,7 @@ func TestIAMSignerHTTPError(t *testing.T) {
conf := &internal.AuthConfig{
Opts: optsWithTokenSource,
ServiceAccountID: "test-service-account",
Version: testVersion,
}
signer, err := newIAMSigner(context.Background(), conf)
if err != nil {
Expand Down Expand Up @@ -182,6 +184,7 @@ func TestIAMSignerUnknownHTTPError(t *testing.T) {
conf := &internal.AuthConfig{
Opts: optsWithTokenSource,
ServiceAccountID: "test-service-account",
Version: testVersion,
}
signer, err := newIAMSigner(context.Background(), conf)
if err != nil {
Expand All @@ -208,7 +211,8 @@ func TestIAMSignerUnknownHTTPError(t *testing.T) {
func TestIAMSignerWithMetadataService(t *testing.T) {
ctx := context.Background()
conf := &internal.AuthConfig{
Opts: optsWithTokenSource,
Opts: optsWithTokenSource,
Version: testVersion,
}

signer, err := newIAMSigner(ctx, conf)
Expand Down Expand Up @@ -253,7 +257,8 @@ func TestIAMSignerWithMetadataService(t *testing.T) {
func TestIAMSignerNoMetadataService(t *testing.T) {
ctx := context.Background()
conf := &internal.AuthConfig{
Opts: optsWithTokenSource,
Opts: optsWithTokenSource,
Version: testVersion,
}

signer, err := newIAMSigner(ctx, conf)
Expand Down Expand Up @@ -340,6 +345,10 @@ func iamServer(t *testing.T, serviceAcct, signature string) *httptest.Server {
if r.URL.Path != wantPath {
t.Errorf("Path = %q; want = %q", r.URL.Path, wantPath)
}
xGoogAPIClientHeader := internal.GetMetricsHeader(testVersion)
if h := r.Header.Get("x-goog-api-client"); h != xGoogAPIClientHeader {
t.Errorf("x-goog-api-client header = %q; want = %q", h, xGoogAPIClientHeader)
}

w.Header().Set("Content-Type", "application/json")
b, err := json.Marshal(resp)
Expand Down
3 changes: 1 addition & 2 deletions auth/user_mgt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (
"net/http"
"net/http/httptest"
"reflect"
"runtime"
"sort"
"strconv"
"strings"
Expand Down Expand Up @@ -2317,7 +2316,7 @@ func echoServer(resp interface{}, t *testing.T) *mockAuthServer {
}

gh = r.Header.Get("x-goog-api-client")
wh = fmt.Sprintf("gl-go/%s fire-admin/%s", strings.TrimPrefix(runtime.Version(), "go"), testVersion)
wh = internal.GetMetricsHeader(testVersion)
if gh != wh {
t.Errorf("x-goog-api-client header = %q; want: %q", gh, wh)
}
Expand Down
2 changes: 2 additions & 0 deletions firebase.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ func (a *App) InstanceID(ctx context.Context) (*iid.Client, error) {
conf := &internal.InstanceIDConfig{
ProjectID: a.projectID,
Opts: a.opts,
Version: Version,
}
return iid.NewClient(ctx, conf)
}
Expand All @@ -133,6 +134,7 @@ func (a *App) Messaging(ctx context.Context) (*messaging.Client, error) {
func (a *App) AppCheck(ctx context.Context) (*appcheck.Client, error) {
conf := &internal.AppCheckConfig{
ProjectID: a.projectID,
Version: Version,
}
return appcheck.NewClient(ctx, conf)
}
Expand Down
3 changes: 3 additions & 0 deletions iid/iid.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ func NewClient(ctx context.Context, c *internal.InstanceIDConfig) (*Client, erro
}

hc, _, err := internal.NewHTTPClient(ctx, c.Opts...)
hc.Opts = []internal.HTTPOption{
internal.WithHeader("x-goog-api-client", internal.GetMetricsHeader(c.Version)),
}
if err != nil {
return nil, err
}
Expand Down
13 changes: 13 additions & 0 deletions iid/iid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ var testIIDConfig = &internal.InstanceIDConfig{
Opts: []option.ClientOption{
option.WithTokenSource(&internal.MockTokenSource{AccessToken: "test-token"}),
},
Version: "test-version",
}

func TestNoProjectID(t *testing.T) {
Expand Down Expand Up @@ -83,6 +84,10 @@ func TestDeleteInstanceID(t *testing.T) {
if h := tr.Header.Get("Authorization"); h != "Bearer test-token" {
t.Errorf("Authorization = %q; want = %q", h, "Bearer test-token")
}
xGoogAPIClientHeader := internal.GetMetricsHeader(testIIDConfig.Version)
if h := tr.Header.Get("x-goog-api-client"); h != xGoogAPIClientHeader {
t.Errorf("x-goog-api-client header = %q; want = %q", h, xGoogAPIClientHeader)
}
}

func TestDeleteInstanceIDError(t *testing.T) {
Expand Down Expand Up @@ -156,6 +161,10 @@ func TestDeleteInstanceIDError(t *testing.T) {
if h := tr.Header.Get("Authorization"); h != "Bearer test-token" {
t.Errorf("Authorization = %q; want = %q", h, "Bearer test-token")
}
xGoogAPIClientHeader := internal.GetMetricsHeader(testIIDConfig.Version)
if h := tr.Header.Get("x-goog-api-client"); h != xGoogAPIClientHeader {
t.Errorf("x-goog-api-client header = %q; want = %q", h, xGoogAPIClientHeader)
}
tr = nil
}
}
Expand Down Expand Up @@ -201,6 +210,10 @@ func TestDeleteInstanceIDUnexpectedError(t *testing.T) {
if h := tr.Header.Get("Authorization"); h != "Bearer test-token" {
t.Errorf("Authorization = %q; want = %q", h, "Bearer test-token")
}
xGoogAPIClientHeader := internal.GetMetricsHeader(testIIDConfig.Version)
if h := tr.Header.Get("x-goog-api-client"); h != xGoogAPIClientHeader {
t.Errorf("x-goog-api-client header = %q; want = %q", h, xGoogAPIClientHeader)
}
}

func TestDeleteInstanceIDConnectionError(t *testing.T) {
Expand Down
7 changes: 7 additions & 0 deletions internal/http_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ import (
"io/ioutil"
"math"
"net/http"
"runtime"
"strconv"
"strings"
"time"

"google.golang.org/api/option"
Expand Down Expand Up @@ -441,3 +443,8 @@ func retryNetworkAndHTTPErrors(statusCodes ...int) RetryCondition {
return false
}
}

func GetMetricsHeader(sdkVersion string) string {

Check failure on line 447 in internal/http_client.go

View workflow job for this annotation

GitHub Actions / stage_release

exported function GetMetricsHeader should have comment or be unexported

Check failure on line 447 in internal/http_client.go

View workflow job for this annotation

GitHub Actions / Module build (1.21)

exported function GetMetricsHeader should have comment or be unexported

Check failure on line 447 in internal/http_client.go

View workflow job for this annotation

GitHub Actions / Module build (1.22)

exported function GetMetricsHeader should have comment or be unexported

Check failure on line 447 in internal/http_client.go

View workflow job for this annotation

GitHub Actions / Module build (1.23)

exported function GetMetricsHeader should have comment or be unexported
goVersion := strings.TrimPrefix(runtime.Version(), "go")
return fmt.Sprintf("gl-go/%s fire-admin/%s", goVersion, sdkVersion)
}
2 changes: 2 additions & 0 deletions internal/internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ type HashConfig map[string]interface{}
type InstanceIDConfig struct {
Opts []option.ClientOption
ProjectID string
Version string
}

// DatabaseConfig represents the configuration of Firebase Database service.
Expand All @@ -76,6 +77,7 @@ type MessagingConfig struct {
// AppCheckConfig represents the configuration of App Check service.
type AppCheckConfig struct {
ProjectID string
Version string
}

// MockTokenSource is a TokenSource implementation that can be used for testing.
Expand Down
6 changes: 2 additions & 4 deletions messaging/messaging.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"fmt"
"net/http"
"regexp"
"runtime"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -878,7 +877,7 @@ func NewClient(ctx context.Context, c *internal.MessagingConfig) (*Client, error

return &Client{
fcmClient: newFCMClient(hc, c, messagingEndpoint, batchEndpoint),
iidClient: newIIDClient(hc),
iidClient: newIIDClient(hc, c),
}, nil
}

Expand All @@ -894,12 +893,11 @@ func newFCMClient(hc *http.Client, conf *internal.MessagingConfig, messagingEndp
client := internal.WithDefaultRetryConfig(hc)
client.CreateErrFn = handleFCMError

goVersion := strings.TrimPrefix(runtime.Version(), "go")
version := fmt.Sprintf("fire-admin-go/%s", conf.Version)
client.Opts = []internal.HTTPOption{
internal.WithHeader(apiFormatVersionHeader, apiFormatVersion),
internal.WithHeader(firebaseClientHeader, version),
internal.WithHeader("x-goog-api-client", fmt.Sprintf("gl-go/%s fire-admin/%s", goVersion, conf.Version)),
internal.WithHeader("x-goog-api-client", internal.GetMetricsHeader(conf.Version)),
}

return &fcmClient{
Expand Down
Loading

0 comments on commit 084440c

Please sign in to comment.