From b6cf20a448e16340f4f176737fd7526f5d462269 Mon Sep 17 00:00:00 2001 From: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> Date: Thu, 7 Nov 2024 15:09:10 -0500 Subject: [PATCH] chore: Add `X-Goog-Api-Client` metric header to outgoing requests (#430) * chore: Add `X-Goog-Api-Client` metric header to outgoing requests * fix styling * fix typo * fix typo * trigger integration tests --- .../Auth/Jwt/HttpPublicKeySourceTest.cs | 4 ++++ .../FirebaseAdmin.Tests/Auth/Jwt/IAMSignerTest.cs | 2 ++ .../Auth/Jwt/IdTokenVerificationTest.cs | 3 +++ .../FirebaseAdmin.Tests/Auth/Jwt/JwtTestUtils.cs | 5 +++++ .../Auth/Jwt/SessionCookieVerificationTest.cs | 3 +++ .../Auth/Multitenancy/TenantManagerTest.cs | 1 + .../Auth/Providers/ProviderTestConfig.cs | 1 + .../Auth/Users/EmailActionRequestTest.cs | 1 + .../Auth/Users/FirebaseUserManagerTest.cs | 2 ++ .../FirebaseAdmin.Tests/CountingMessageHandler.cs | 2 +- .../FirebaseAdmin.Tests/FirebaseAdmin.Tests.csproj | 1 + .../Messaging/FirebaseMessagingClientTest.cs | 9 +++++++++ .../FirebaseAdmin.Tests/MockHttpClientFactory.cs | 2 +- .../Util/ErrorHandlingHttpClientTest.cs | 5 +++++ .../FirebaseAdmin/Messaging/FirebaseMessagingClient.cs | 1 + .../FirebaseAdmin/Messaging/InstanceIdClient.cs | 1 + .../FirebaseAdmin/Util/ErrorHandlingHttpClient.cs | 1 + FirebaseAdmin/FirebaseAdmin/Util/HttpUtils.cs | 6 ++++++ 18 files changed, 48 insertions(+), 2 deletions(-) diff --git a/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Jwt/HttpPublicKeySourceTest.cs b/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Jwt/HttpPublicKeySourceTest.cs index b35600ea..9ba5a5f4 100644 --- a/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Jwt/HttpPublicKeySourceTest.cs +++ b/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Jwt/HttpPublicKeySourceTest.cs @@ -44,6 +44,8 @@ public async Task GetPublicKeysWithoutCaching() Assert.Equal(2, keys.Count); Assert.Equal(2, handler.Calls); Assert.NotSame(keys, keys2); + JwtTestUtils.AssertRequest(handler.Requests[0]); + JwtTestUtils.AssertRequest(handler.Requests[1]); } [Fact] @@ -77,6 +79,8 @@ public async Task GetPublicKeysWithCaching() Assert.Equal(2, keys.Count); Assert.Equal(2, handler.Calls); Assert.NotSame(keys, keys3); + JwtTestUtils.AssertRequest(handler.Requests[0]); + JwtTestUtils.AssertRequest(handler.Requests[1]); } [Fact] diff --git a/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Jwt/IAMSignerTest.cs b/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Jwt/IAMSignerTest.cs index a93cd8e5..010172da 100644 --- a/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Jwt/IAMSignerTest.cs +++ b/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Jwt/IAMSignerTest.cs @@ -57,6 +57,7 @@ public async Task Signer() Assert.Equal(Convert.ToBase64String(data), req.BytesToSign); Assert.Equal(2, handler.Calls); Assert.Equal("Bearer token", handler.LastRequestHeaders.Authorization?.ToString()); + JwtTestUtils.AssertRequest(handler.Requests[1]); } [Fact] @@ -112,6 +113,7 @@ public async Task Signer() handler.LastRequestBody); Assert.Equal(Convert.ToBase64String(data), req.BytesToSign); Assert.Equal(1, handler.Calls); + JwtTestUtils.AssertRequest(Assert.Single(handler.Requests)); } [Fact] diff --git a/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Jwt/IdTokenVerificationTest.cs b/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Jwt/IdTokenVerificationTest.cs index 0c0d6ffa..e04fbaa4 100644 --- a/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Jwt/IdTokenVerificationTest.cs +++ b/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Jwt/IdTokenVerificationTest.cs @@ -434,6 +434,7 @@ public async Task RevokedToken(TestConfig config) this.CheckException(exception, expectedMessage, AuthErrorCode.RevokedIdToken); Assert.Equal(1, handler.Calls); config.AssertRevocationCheckRequest(handler.Requests[0].Url); + JwtTestUtils.AssertRequest(Assert.Single(handler.Requests)); } [Theory] @@ -458,6 +459,7 @@ public async Task ValidUnrevokedToken(TestConfig config) Assert.Equal("testuser", decoded.Uid); Assert.Equal(1, handler.Calls); config.AssertRevocationCheckRequest(handler.Requests[0].Url); + JwtTestUtils.AssertRequest(Assert.Single(handler.Requests)); } [Theory] @@ -484,6 +486,7 @@ public async Task CheckRevokedError(TestConfig config) Assert.NotNull(exception.HttpResponse); Assert.Equal(1, handler.Calls); config.AssertRevocationCheckRequest(handler.Requests[0].Url); + JwtTestUtils.AssertRequest(Assert.Single(handler.Requests)); } [Theory] diff --git a/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Jwt/JwtTestUtils.cs b/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Jwt/JwtTestUtils.cs index 6972b4ac..8e01bca2 100644 --- a/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Jwt/JwtTestUtils.cs +++ b/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Jwt/JwtTestUtils.cs @@ -100,6 +100,11 @@ public static void AssertRevocationCheckRequest(string tenantId, string emulator Assert.Equal(expectedUrl, uri.ToString()); } + internal static void AssertRequest(MockMessageHandler.IncomingRequest request) + { + Assert.Contains(HttpUtils.GetMetricsHeader(), request.Headers.GetValues("X-Goog-Api-Client")); + } + private static ISigner CreateTestSigner(string filePath) { var credential = GoogleCredential.FromFile(filePath); diff --git a/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Jwt/SessionCookieVerificationTest.cs b/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Jwt/SessionCookieVerificationTest.cs index 100b8d62..37f78874 100644 --- a/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Jwt/SessionCookieVerificationTest.cs +++ b/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Jwt/SessionCookieVerificationTest.cs @@ -331,6 +331,7 @@ public async Task RevokedToken(TestConfig config) this.CheckException(exception, expectedMessage, AuthErrorCode.RevokedSessionCookie); Assert.Equal(1, handler.Calls); JwtTestUtils.AssertRevocationCheckRequest(null, handler.Requests[0].Url); + JwtTestUtils.AssertRequest(Assert.Single(handler.Requests)); } [Theory] @@ -355,6 +356,7 @@ public async Task ValidUnrevokedToken(TestConfig config) Assert.Equal("testuser", decoded.Uid); Assert.Equal(1, handler.Calls); JwtTestUtils.AssertRevocationCheckRequest(null, handler.Requests[0].Url); + JwtTestUtils.AssertRequest(Assert.Single(handler.Requests)); } [Theory] @@ -381,6 +383,7 @@ public async Task CheckRevokedError(TestConfig config) Assert.NotNull(exception.HttpResponse); Assert.Equal(1, handler.Calls); JwtTestUtils.AssertRevocationCheckRequest(null, handler.Requests[0].Url); + JwtTestUtils.AssertRequest(Assert.Single(handler.Requests)); } private void CheckException( diff --git a/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Multitenancy/TenantManagerTest.cs b/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Multitenancy/TenantManagerTest.cs index 25796e41..9ca89f1e 100644 --- a/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Multitenancy/TenantManagerTest.cs +++ b/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Multitenancy/TenantManagerTest.cs @@ -690,6 +690,7 @@ internal void AssertRequest( } Assert.Contains(ClientVersion, request.Headers.GetValues("X-Client-Version")); + Assert.Contains(HttpUtils.GetMetricsHeader(), request.Headers.GetValues("X-Goog-Api-Client")); } } diff --git a/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Providers/ProviderTestConfig.cs b/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Providers/ProviderTestConfig.cs index 38535cbc..2e93f52d 100644 --- a/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Providers/ProviderTestConfig.cs +++ b/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Providers/ProviderTestConfig.cs @@ -109,6 +109,7 @@ internal void AssertRequest( var expectedPath = $"/v2/projects/{MockProjectId}{tenantInfo}/{expectedSuffix}"; Assert.Equal(expectedPath, request.Url.PathAndQuery); Assert.Contains(ClientVersion, request.Headers.GetValues("X-Client-Version")); + Assert.Contains(HttpUtils.GetMetricsHeader(), request.Headers.GetValues("X-Goog-Api-Client")); } public class InvalidListOptions : IEnumerable diff --git a/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Users/EmailActionRequestTest.cs b/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Users/EmailActionRequestTest.cs index fe3a8ec4..73778fe1 100644 --- a/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Users/EmailActionRequestTest.cs +++ b/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Users/EmailActionRequestTest.cs @@ -372,6 +372,7 @@ private void AssertRequest(MockMessageHandler.IncomingRequest message) Assert.Equal( FirebaseUserManager.ClientVersion, message.Headers.GetValues(FirebaseUserManager.ClientVersionHeader).First()); + Assert.Equal(HttpUtils.GetMetricsHeader(), message.Headers.GetValues("X-Goog-Api-Client").First()); } } } diff --git a/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Users/FirebaseUserManagerTest.cs b/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Users/FirebaseUserManagerTest.cs index 616a8120..4b74fa75 100644 --- a/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Users/FirebaseUserManagerTest.cs +++ b/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Users/FirebaseUserManagerTest.cs @@ -2222,6 +2222,8 @@ internal void AssertRequest( var expectedUrl = $"https://{IdToolkitUrl}{tenant}/{expectedSuffix}"; Assert.Equal(expectedUrl, request.Url.ToString()); } + + Assert.Equal(HttpUtils.GetMetricsHeader(), request.Headers.GetValues("X-Goog-Api-Client").First()); } private IDictionary GetUserResponseDictionary(string response = null) diff --git a/FirebaseAdmin/FirebaseAdmin.Tests/CountingMessageHandler.cs b/FirebaseAdmin/FirebaseAdmin.Tests/CountingMessageHandler.cs index de559af8..1d8ce6db 100644 --- a/FirebaseAdmin/FirebaseAdmin.Tests/CountingMessageHandler.cs +++ b/FirebaseAdmin/FirebaseAdmin.Tests/CountingMessageHandler.cs @@ -41,4 +41,4 @@ protected sealed override Task SendAsync( protected abstract Task DoSendAsync( HttpRequestMessage request, int count, CancellationToken cancellationToken); } -} \ No newline at end of file +} diff --git a/FirebaseAdmin/FirebaseAdmin.Tests/FirebaseAdmin.Tests.csproj b/FirebaseAdmin/FirebaseAdmin.Tests/FirebaseAdmin.Tests.csproj index c99fa27d..3c2f0485 100644 --- a/FirebaseAdmin/FirebaseAdmin.Tests/FirebaseAdmin.Tests.csproj +++ b/FirebaseAdmin/FirebaseAdmin.Tests/FirebaseAdmin.Tests.csproj @@ -28,6 +28,7 @@ all + diff --git a/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/FirebaseMessagingClientTest.cs b/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/FirebaseMessagingClientTest.cs index 7ca8e114..73c5c211 100644 --- a/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/FirebaseMessagingClientTest.cs +++ b/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/FirebaseMessagingClientTest.cs @@ -173,6 +173,7 @@ public async Task SendEachAsync() Assert.Equal("projects/fir-adminintegrationtests/messages/8580920590356323124", response.Responses[0].MessageId); Assert.Equal("projects/fir-adminintegrationtests/messages/5903525881088369386", response.Responses[1].MessageId); Assert.Equal(2, handler.Calls); + this.CheckHeaders(handler.LastRequestHeaders); } [Fact] @@ -245,6 +246,7 @@ public async Task SendEachAsyncWithError() Assert.NotNull(exception.HttpResponse); Assert.Equal(2, handler.Calls); + this.CheckHeaders(handler.LastRequestHeaders); } [Fact] @@ -311,6 +313,7 @@ public async Task SendEachAsyncWithErrorNoDetail() Assert.NotNull(exception.HttpResponse); Assert.Equal(2, handler.Calls); + this.CheckHeaders(handler.LastRequestHeaders); } [Fact] @@ -404,6 +407,7 @@ public async Task SendAllAsync() Assert.Equal("fire-admin-dotnet", userAgent.Product.Name); Assert.Equal(2, this.CountLinesWithPrefix(handler.LastRequestBody, VersionHeader)); Assert.Equal(2, this.CountLinesWithPrefix(handler.LastRequestBody, ApiFormatHeader)); + Assert.Equal(2, this.CountLinesWithPrefix(handler.LastRequestBody, $"X-Goog-Api-Client: {HttpUtils.GetMetricsHeader()}")); } [Fact] @@ -486,6 +490,7 @@ public async Task SendAllAsyncWithError() Assert.Equal(1, handler.Calls); Assert.Equal(2, this.CountLinesWithPrefix(handler.LastRequestBody, VersionHeader)); Assert.Equal(2, this.CountLinesWithPrefix(handler.LastRequestBody, ApiFormatHeader)); + Assert.Equal(2, this.CountLinesWithPrefix(handler.LastRequestBody, $"X-Goog-Api-Client: {HttpUtils.GetMetricsHeader()}")); } [Fact] @@ -564,6 +569,7 @@ public async Task SendAllAsyncWithErrorNoDetail() Assert.Equal(1, handler.Calls); Assert.Equal(2, this.CountLinesWithPrefix(handler.LastRequestBody, VersionHeader)); Assert.Equal(2, this.CountLinesWithPrefix(handler.LastRequestBody, ApiFormatHeader)); + Assert.Equal(2, this.CountLinesWithPrefix(handler.LastRequestBody, $"X-Goog-Api-Client: {HttpUtils.GetMetricsHeader()}")); } [Fact] @@ -778,6 +784,9 @@ private void CheckHeaders(HttpRequestHeaders header) var apiFormatHeader = header.GetValues("X-GOOG-API-FORMAT-VERSION").First(); Assert.Equal("2", apiFormatHeader); + + var metricsHeader = header.GetValues("X-Goog-Api-Client").First(); + Assert.Equal(HttpUtils.GetMetricsHeader(), metricsHeader); } private int CountLinesWithPrefix(string body, string linePrefix) diff --git a/FirebaseAdmin/FirebaseAdmin.Tests/MockHttpClientFactory.cs b/FirebaseAdmin/FirebaseAdmin.Tests/MockHttpClientFactory.cs index 5c392007..9f3912fe 100644 --- a/FirebaseAdmin/FirebaseAdmin.Tests/MockHttpClientFactory.cs +++ b/FirebaseAdmin/FirebaseAdmin.Tests/MockHttpClientFactory.cs @@ -31,4 +31,4 @@ protected override HttpMessageHandler CreateHandler(CreateHttpClientArgs args) return this.handler; } } -} \ No newline at end of file +} diff --git a/FirebaseAdmin/FirebaseAdmin.Tests/Util/ErrorHandlingHttpClientTest.cs b/FirebaseAdmin/FirebaseAdmin.Tests/Util/ErrorHandlingHttpClientTest.cs index c9bf80b5..388b2e64 100644 --- a/FirebaseAdmin/FirebaseAdmin.Tests/Util/ErrorHandlingHttpClientTest.cs +++ b/FirebaseAdmin/FirebaseAdmin.Tests/Util/ErrorHandlingHttpClientTest.cs @@ -47,6 +47,7 @@ public async Task SuccessfulRequest() Assert.Single(response.Result); Assert.Equal("bar", response.Result["foo"]); Assert.Equal(1, handler.Calls); + Assert.Equal(HttpUtils.GetMetricsHeader(), handler.LastRequestHeaders.GetValues("X-Goog-Api-Client").First()); } [Fact] @@ -69,6 +70,7 @@ public async Task SuccessfulAuthorizedRequest() Assert.Single(response.Result); Assert.Equal("bar", response.Result["foo"]); Assert.Equal(1, handler.Calls); + Assert.Equal(HttpUtils.GetMetricsHeader(), handler.LastRequestHeaders.GetValues("X-Goog-Api-Client").First()); Assert.Equal( "Bearer test-token", handler.LastRequestHeaders.GetValues("Authorization").First()); @@ -166,6 +168,7 @@ public async Task CustomDeserializer() Assert.Equal("bar", response.Result["foo"]); Assert.Equal(1, handler.Calls); Assert.Equal(1, deserializer.Count); + Assert.Equal(HttpUtils.GetMetricsHeader(), handler.LastRequestHeaders.GetValues("X-Goog-Api-Client").First()); } [Fact] @@ -244,6 +247,7 @@ public async Task RetryOnErrorResponse() Assert.Null(exception.InnerException); Assert.NotNull(exception.HttpResponse); Assert.Equal(5, handler.Calls); + Assert.Equal(HttpUtils.GetMetricsHeader(), handler.LastRequestHeaders.GetValues("X-Goog-Api-Client").First()); } [Fact] @@ -267,6 +271,7 @@ public async Task RetryOnNetworkError() Assert.Same(handler.Exception, exception.InnerException); Assert.Null(exception.HttpResponse); Assert.Equal(5, handler.Calls); + Assert.Equal(HttpUtils.GetMetricsHeader(), handler.LastRequestHeaders.GetValues("X-Goog-Api-Client").First()); } [Fact] diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/FirebaseMessagingClient.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/FirebaseMessagingClient.cs index f75981ea..e27745ae 100644 --- a/FirebaseAdmin/FirebaseAdmin/Messaging/FirebaseMessagingClient.cs +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/FirebaseMessagingClient.cs @@ -404,6 +404,7 @@ public FCMClientServiceRequest(FCMClientService clientService, string restPath, this.ModifyRequest = (request) => { AddCommonHeaders(request); + request.Headers.Add("X-Goog-Api-Client", HttpUtils.GetMetricsHeader()); }; this.InitParameters(); } diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/InstanceIdClient.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/InstanceIdClient.cs index 55617fc9..388e16a9 100644 --- a/FirebaseAdmin/FirebaseAdmin/Messaging/InstanceIdClient.cs +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/InstanceIdClient.cs @@ -126,6 +126,7 @@ private async Task SendInstanceIdRequest( }; request.Headers.Add("access_token_auth", "true"); + request.Headers.Add("X-Goog-Api-Client", HttpUtils.GetMetricsHeader()); var response = await this.httpClient .SendAndDeserializeAsync(request) diff --git a/FirebaseAdmin/FirebaseAdmin/Util/ErrorHandlingHttpClient.cs b/FirebaseAdmin/FirebaseAdmin/Util/ErrorHandlingHttpClient.cs index ae265786..01f1f0c3 100644 --- a/FirebaseAdmin/FirebaseAdmin/Util/ErrorHandlingHttpClient.cs +++ b/FirebaseAdmin/FirebaseAdmin/Util/ErrorHandlingHttpClient.cs @@ -59,6 +59,7 @@ public void Dispose() internal async Task> SendAndDeserializeAsync( HttpRequestMessage request, CancellationToken cancellationToken = default) { + request.Headers.Add("X-Goog-Api-Client", HttpUtils.GetMetricsHeader()); var info = await this.SendAndReadAsync(request, cancellationToken) .ConfigureAwait(false); try diff --git a/FirebaseAdmin/FirebaseAdmin/Util/HttpUtils.cs b/FirebaseAdmin/FirebaseAdmin/Util/HttpUtils.cs index 8b57c7ec..d946deba 100644 --- a/FirebaseAdmin/FirebaseAdmin/Util/HttpUtils.cs +++ b/FirebaseAdmin/FirebaseAdmin/Util/HttpUtils.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; @@ -51,6 +52,11 @@ internal static IList CreateUpdateMask(object request) return mask; } + internal static string GetMetricsHeader() + { + return $"gl-dotnet/{Environment.Version} fire-admin/{FirebaseApp.GetSdkVersion()}"; + } + private static List CreateUpdateMask(JObject dictionary) { var mask = new List();