| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | using Google.Apis.Auth.OAuth2; |
| | using Google.Apis.Auth.OAuth2.Responses; |
| | using Google.Apis.Http; |
| | using Google.Apis.Json; |
| | using Google.Apis.Tests.Mocks; |
| | using System; |
| | using System.Linq; |
| | using System.Net; |
| | using System.Net.Http; |
| | using System.Text; |
| | using System.Threading.Tasks; |
| | using Xunit; |
| | using static Google.Apis.Auth.JsonWebSignature; |
| |
|
| | namespace Google.Apis.Auth.Tests.OAuth2 |
| | { |
| | |
| | public class ComputeCredentialTests |
| | { |
| | private const string FakeUniverseDomain = "fake.universe.domain.com"; |
| |
|
| | [Fact] |
| | public void IsRunningOnComputeEngine_ResultIsCached() |
| | { |
| | |
| | Assert.Same(ComputeCredential.IsRunningOnComputeEngine(), |
| | ComputeCredential.IsRunningOnComputeEngine()); |
| | } |
| |
|
| | [Fact] |
| | public async Task UniverseDomain_Custom() |
| | { |
| | var credential = new ComputeCredential(new ComputeCredential.Initializer |
| | { |
| | UniverseDomain = FakeUniverseDomain |
| | }) as IGoogleCredential; |
| |
|
| | Assert.Equal(FakeUniverseDomain, await credential.GetUniverseDomainAsync(default)); |
| | Assert.Equal(FakeUniverseDomain, credential.GetUniverseDomain()); |
| | } |
| |
|
| | [Fact] |
| | public async Task WithUniverseDomain() |
| | { |
| | var credential = new ComputeCredential() as IGoogleCredential; |
| |
|
| | var newCredential = credential.WithUniverseDomain(FakeUniverseDomain); |
| |
|
| | Assert.NotSame(credential, newCredential); |
| | Assert.IsType<ComputeCredential>(newCredential); |
| |
|
| | Assert.Equal(FakeUniverseDomain, await newCredential.GetUniverseDomainAsync(default)); |
| | Assert.Equal(FakeUniverseDomain, newCredential.GetUniverseDomain()); |
| | } |
| |
|
| | [Fact] |
| | public void WithHttpClientFactory() |
| | { |
| | var credential = new ComputeCredential(); |
| | var factory = new HttpClientFactory(); |
| | var credentialWithFactory = Assert.IsType<ComputeCredential>(((IGoogleCredential)credential).WithHttpClientFactory(factory)); |
| |
|
| | Assert.NotSame(credential, credentialWithFactory); |
| | Assert.NotSame(credential.HttpClient, credentialWithFactory.HttpClient); |
| | Assert.NotSame(credential.HttpClientFactory, credentialWithFactory.HttpClientFactory); |
| | Assert.Same(factory, credentialWithFactory.HttpClientFactory); |
| | } |
| |
|
| | [Fact] |
| | public async Task FetchesDefaultServiceAccountEmail() |
| | { |
| | string fakeServiceAccountEmail = "fake-service-account@fake-instance.com"; |
| |
|
| | var messageHandler = new DelegatedMessageHandler(FetchServiceAccountId); |
| | var initializer = new ComputeCredential.Initializer("http://will.be.ignored", "http://will.be.ignored") |
| | { |
| | HttpClientFactory = new MockHttpClientFactory(messageHandler) |
| | }; |
| | var credential = new ComputeCredential(initializer); |
| |
|
| | var serviceAccountEmailTask = credential.GetDefaultServiceAccountEmailAsync(); |
| |
|
| | Assert.Equal(fakeServiceAccountEmail, await serviceAccountEmailTask); |
| |
|
| | Task<HttpResponseMessage> FetchServiceAccountId(HttpRequestMessage request) |
| | { |
| | Assert.Equal(GoogleAuthConsts.EffectiveComputeDefaultServiceAccountEmailUrl, request.RequestUri.ToString()); |
| | Assert.Null(request.Headers.Authorization); |
| | Assert.Contains(request.Headers, h => h.Key == ComputeCredential.MetadataFlavor && h.Value.Contains(ComputeCredential.GoogleMetadataHeader)); |
| | return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) |
| | { |
| | Content = new StringContent(fakeServiceAccountEmail) |
| | }); |
| | } |
| | } |
| |
|
| | [Fact] |
| | public async Task FetchesOidcToken() |
| | { |
| | |
| | var clock = new MockClock(new DateTime(2020, 5, 21, 9, 20, 0, 0, DateTimeKind.Utc)); |
| | var messageHandler = new OidcComputeSuccessMessageHandler(); |
| | var initializer = new ComputeCredential.Initializer("http://will.be.ignored", "http://will.be.ignored") |
| | { |
| | Clock = clock, |
| | HttpClientFactory = new MockHttpClientFactory(messageHandler) |
| | }; |
| | var credential = new ComputeCredential(initializer); |
| |
|
| | |
| | |
| | var oidcToken = await credential.GetOidcTokenAsync(OidcTokenOptions.FromTargetAudience("will.be.ignored")); |
| |
|
| | var signedToken = SignedToken<Header, Payload>.FromSignedToken(await oidcToken.GetAccessTokenAsync()); |
| | Assert.Equal("https://first_call.test", signedToken.Payload.Audience); |
| | |
| | clock.UtcNow = clock.UtcNow.AddMinutes(20); |
| | signedToken = SignedToken<Header, Payload>.FromSignedToken(await oidcToken.GetAccessTokenAsync()); |
| | Assert.Equal("https://first_call.test", signedToken.Payload.Audience); |
| | |
| | Assert.Equal(1, messageHandler.Calls); |
| | } |
| |
|
| | [Fact] |
| | public async Task RefreshesOidcToken() |
| | { |
| | |
| | var clock = new MockClock(new DateTime(2020, 5, 21, 9, 20, 0, 0, DateTimeKind.Utc)); |
| | var messageHandler = new OidcComputeSuccessMessageHandler(); |
| | var initializer = new ComputeCredential.Initializer("http://will.be.ignored", "http://will.be.ignored") |
| | { |
| | Clock = clock, |
| | HttpClientFactory = new MockHttpClientFactory(messageHandler) |
| | }; |
| | var credential = new ComputeCredential(initializer); |
| |
|
| | |
| | |
| | var oidcToken = await credential.GetOidcTokenAsync(OidcTokenOptions.FromTargetAudience("will.be.ignored")); |
| |
|
| | var signedToken = SignedToken<Header, Payload>.FromSignedToken(await oidcToken.GetAccessTokenAsync()); |
| | Assert.Equal("https://first_call.test", signedToken.Payload.Audience); |
| | |
| | clock.UtcNow = clock.UtcNow.AddHours(2); |
| | signedToken = SignedToken<Header, Payload>.FromSignedToken(await oidcToken.GetAccessTokenAsync()); |
| | Assert.Equal("https://subsequent_calls.test", signedToken.Payload.Audience); |
| | |
| | Assert.Equal(2, messageHandler.Calls); |
| | } |
| |
|
| | [Fact] |
| | public async Task FetchesOidcToken_WithDefaultOptions() |
| | { |
| | |
| | var clock = new MockClock(new DateTime(2020, 5, 21, 9, 20, 0, 0, DateTimeKind.Utc)); |
| | var messageHandler = new OidcComputeSuccessMessageHandler(); |
| | var initializer = new ComputeCredential.Initializer("http://will.be.ignored", "http://will.be.ignored") |
| | { |
| | Clock = clock, |
| | HttpClientFactory = new MockHttpClientFactory(messageHandler) |
| | }; |
| | var credential = new ComputeCredential(initializer); |
| |
|
| | var oidcToken = await credential.GetOidcTokenAsync(OidcTokenOptions.FromTargetAudience("any_audience")); |
| | await oidcToken.GetAccessTokenAsync(); |
| |
|
| | Assert.Equal("?audience=any_audience&format=full", messageHandler.LatestRequest.RequestUri.Query); |
| | } |
| |
|
| | [Theory] |
| | [InlineData(OidcTokenFormat.Full, "another_audience", "?audience=another_audience&format=full")] |
| | [InlineData(OidcTokenFormat.Standard, "another_audience", "?audience=another_audience")] |
| | [InlineData(OidcTokenFormat.FullWithLicences, "another_audience", "?audience=another_audience&format=full&licenses=true")] |
| | public async Task FetchesOidcToken_WithOptions(OidcTokenFormat format, string targetAudience, string expectedQueryString) |
| | { |
| | |
| | var clock = new MockClock(new DateTime(2020, 5, 21, 9, 20, 0, 0, DateTimeKind.Utc)); |
| | var messageHandler = new OidcComputeSuccessMessageHandler(); |
| | var initializer = new ComputeCredential.Initializer("http://will.be.ignored", "http://will.be.ignored") |
| | { |
| | Clock = clock, |
| | HttpClientFactory = new MockHttpClientFactory(messageHandler) |
| | }; |
| | var credential = new ComputeCredential(initializer); |
| |
|
| | var oidcToken = await credential.GetOidcTokenAsync( |
| | OidcTokenOptions.FromTargetAudience("any_audience") |
| | .WithTargetAudience(targetAudience) |
| | .WithTokenFormat(format)); |
| | await oidcToken.GetAccessTokenAsync(); |
| |
|
| | Assert.Equal(expectedQueryString, messageHandler.LatestRequest.RequestUri.Query); |
| | } |
| |
|
| | public static TheoryData<string[], string> Scoped_WithDefaultTokenUrl_Data => |
| | new TheoryData<string[], string> |
| | { |
| | |
| | { null, GoogleAuthConsts.EffectiveComputeTokenUrl }, |
| | { new string[] { "scope1", "scope2"}, $"{GoogleAuthConsts.EffectiveComputeTokenUrl}?scopes=scope1,scope2" } |
| | }; |
| |
|
| | public static TheoryData<string[], string, string> Scoped_WithCustomTokenUrl_Data => |
| | new TheoryData<string[], string, string> |
| | { |
| | |
| | { null, "https://custom.metadata.server/compute/token", "https://custom.metadata.server/compute/token" }, |
| | { null, "https://custom.metadata.server/compute/token?parameter=value", "https://custom.metadata.server/compute/token?parameter=value" }, |
| | { new string[] { "scope1", "scope2" }, "https://custom.metadata.server/compute/token", "https://custom.metadata.server/compute/token?scopes=scope1,scope2" }, |
| | { new string[] { "scope1", "scope2" }, "https://custom.metadata.server/compute/token?parameter=value", "https://custom.metadata.server/compute/token?parameter=value&scopes=scope1,scope2" } |
| | }; |
| |
|
| | private void AssertScoped(ComputeCredential credential, string[] scopes, string expectedTokenUrl) |
| | { |
| | Assert.Collection(credential.Scopes ?? Enumerable.Empty<string>(), |
| | (scopes?.Select<string, Action<string>>(expectedScope => actualScope => Assert.Equal(expectedScope, actualScope)) ?? Enumerable.Empty<Action<string>>()).ToArray()); |
| | Assert.Equal(expectedTokenUrl, credential.EffectiveTokenServerUrl); |
| | } |
| |
|
| | private async Task AssertUsesScopedUrl(ComputeCredential credential, FetchesTokenMessageHandler fakeMessageHandler, string expectedTokenUrl) |
| | { |
| | Assert.NotNull(await credential.GetAccessTokenForRequestAsync()); |
| | Assert.Equal(1, fakeMessageHandler.Calls); |
| | Assert.Equal(expectedTokenUrl, fakeMessageHandler.Requests.First().RequestUri.AbsoluteUri); |
| | } |
| |
|
| | [Theory] |
| | [MemberData(nameof(Scoped_WithDefaultTokenUrl_Data))] |
| | public async Task Scoped_Initializer_WithDefaultTokenUrl(string[] scopes, string expectedTokenUrl) |
| | { |
| | var fakeMessageHandler = new FetchesTokenMessageHandler(); |
| | var credential = new ComputeCredential(new ComputeCredential.Initializer() |
| | { |
| | Scopes = scopes, |
| | HttpClientFactory = new MockHttpClientFactory(fakeMessageHandler) |
| | }); |
| |
|
| | AssertScoped(credential, scopes, expectedTokenUrl); |
| | await AssertUsesScopedUrl(credential, fakeMessageHandler, expectedTokenUrl); |
| | } |
| |
|
| | [Theory] |
| | [MemberData(nameof(Scoped_WithDefaultTokenUrl_Data))] |
| | public async Task Scoped_MaybeWithScopes_WithDefaultTokenUrl(string[] scopes, string expectedTokenUrl) |
| | { |
| | var fakeMessageHandler = new FetchesTokenMessageHandler(); |
| | var credential = (new ComputeCredential(new ComputeCredential.Initializer() |
| | { |
| | HttpClientFactory = new MockHttpClientFactory(fakeMessageHandler) |
| | }) as IGoogleCredential).MaybeWithScopes(scopes) as ComputeCredential; |
| | |
| | AssertScoped(credential, scopes, expectedTokenUrl); |
| | await AssertUsesScopedUrl(credential, fakeMessageHandler, expectedTokenUrl); |
| | } |
| |
|
| | [Theory] |
| | [MemberData(nameof(Scoped_WithCustomTokenUrl_Data))] |
| | public async Task Scoped_Initializer_WithCustomTokenUrl(string[] scopes, string customTokenUrl, string expectedTokenUrl) |
| | { |
| | var fakeMessageHandler = new FetchesTokenMessageHandler(); |
| | var credential = new ComputeCredential(new ComputeCredential.Initializer(customTokenUrl) |
| | { |
| | Scopes = scopes, |
| | HttpClientFactory = new MockHttpClientFactory(fakeMessageHandler) |
| | }); |
| |
|
| | AssertScoped(credential, scopes, expectedTokenUrl); |
| | await AssertUsesScopedUrl(credential, fakeMessageHandler, expectedTokenUrl); |
| | } |
| |
|
| | [Theory] |
| | [MemberData(nameof(Scoped_WithCustomTokenUrl_Data))] |
| | public async Task Scoped_MaybeWithScopes_WithCustomTokenUrl(string[] scopes, string customTokenUrl, string expectedTokenUrl) |
| | { |
| | var fakeMessageHandler = new FetchesTokenMessageHandler(); |
| | var credential = (new ComputeCredential(new ComputeCredential.Initializer(customTokenUrl) |
| | { |
| | HttpClientFactory = new MockHttpClientFactory(fakeMessageHandler) |
| | }) as IGoogleCredential).MaybeWithScopes(scopes) as ComputeCredential; |
| |
|
| | AssertScoped(credential, scopes, expectedTokenUrl); |
| | await AssertUsesScopedUrl(credential, fakeMessageHandler, expectedTokenUrl); |
| | } |
| |
|
| | [Fact] |
| | public async Task SignBlob_Default_RecommendedRetryPolicy() |
| | { |
| | var initializer = GetInitializerForSignBlob(); |
| | var mockFactory = initializer.HttpClientFactory as MockHttpClientFactory; |
| | var credential = new ComputeCredential(initializer); |
| |
|
| | await credential.SignBlobAsync(Encoding.ASCII.GetBytes("ignored")); |
| |
|
| | |
| | |
| | |
| | |
| | Assert.Equal(3, mockFactory.AllCreateHttpClientArgs.Count()); |
| | var signBlobArgs = mockFactory.AllCreateHttpClientArgs.Last(); |
| |
|
| | |
| | Assert.Equal(2, signBlobArgs.Initializers.Count()); |
| | Assert.Contains(signBlobArgs.Initializers, initializer => initializer is ComputeCredential); |
| | Assert.Contains(signBlobArgs.Initializers, initializer => initializer == GoogleAuthConsts.IamSignBlobEndpointRecommendedRetry); |
| | } |
| |
|
| | [Fact] |
| | public async Task SignBlob_BadResponse503AndRecommended_RecommendedRetryPolicy() |
| | { |
| | var initializer = GetInitializerForSignBlob(); |
| | var mockFactory = initializer.HttpClientFactory as MockHttpClientFactory; |
| |
|
| | initializer.DefaultExponentialBackOffPolicy = ExponentialBackOffPolicy.UnsuccessfulResponse503 | ExponentialBackOffPolicy.RecommendedOrDefault; |
| | var credential = new ComputeCredential(initializer); |
| |
|
| | await credential.SignBlobAsync(Encoding.ASCII.GetBytes("ignored")); |
| |
|
| | |
| | |
| | |
| | |
| | Assert.Equal(3, mockFactory.AllCreateHttpClientArgs.Count()); |
| | var signBlobArgs = mockFactory.AllCreateHttpClientArgs.Last(); |
| |
|
| | |
| | Assert.Equal(2, signBlobArgs.Initializers.Count()); |
| | Assert.Contains(signBlobArgs.Initializers, initializer => initializer is ComputeCredential); |
| | Assert.Contains(signBlobArgs.Initializers, initializer => initializer == GoogleAuthConsts.IamSignBlobEndpointRecommendedRetry); |
| | } |
| |
|
| | [Fact] |
| | public async Task SignBlob_ExceptionAndRecommended_RecommendedAndOtherRetryPolicy() |
| | { |
| | var initializer = GetInitializerForSignBlob(); |
| | var mockFactory = initializer.HttpClientFactory as MockHttpClientFactory; |
| |
|
| | initializer.DefaultExponentialBackOffPolicy = ExponentialBackOffPolicy.Exception | ExponentialBackOffPolicy.RecommendedOrDefault; |
| | var credential = new ComputeCredential(initializer); |
| |
|
| | await credential.SignBlobAsync(Encoding.ASCII.GetBytes("ignored")); |
| |
|
| | |
| | |
| | |
| | |
| | Assert.Equal(3, mockFactory.AllCreateHttpClientArgs.Count()); |
| | var signBlobArgs = mockFactory.AllCreateHttpClientArgs.Last(); |
| |
|
| | |
| | Assert.Equal(3, signBlobArgs.Initializers.Count()); |
| | Assert.Contains(signBlobArgs.Initializers, initializer => initializer is ComputeCredential); |
| | Assert.Contains(signBlobArgs.Initializers, initializer => initializer == GoogleAuthConsts.IamSignBlobEndpointRecommendedRetry); |
| | Assert.Contains(signBlobArgs.Initializers, initializer => initializer is ExponentialBackOffInitializer && initializer != GoogleAuthConsts.IamSignBlobEndpointRecommendedRetry); |
| | } |
| |
|
| | [Fact] |
| | public async Task SignBlob_NoRetryPolicy() |
| | { |
| | var initializer = GetInitializerForSignBlob(); |
| | var mockFactory = initializer.HttpClientFactory as MockHttpClientFactory; |
| |
|
| | initializer.DefaultExponentialBackOffPolicy = ExponentialBackOffPolicy.None; |
| | var credential = new ComputeCredential(initializer); |
| |
|
| | await credential.SignBlobAsync(Encoding.ASCII.GetBytes("ignored")); |
| |
|
| | |
| | |
| | |
| | |
| | Assert.Equal(3, mockFactory.AllCreateHttpClientArgs.Count()); |
| | var signBlobArgs = mockFactory.AllCreateHttpClientArgs.Last(); |
| |
|
| | |
| | var clientInitializer = Assert.Single(signBlobArgs.Initializers); |
| | Assert.IsType<ComputeCredential>(clientInitializer); |
| | } |
| |
|
| | [Theory] |
| | [InlineData(ExponentialBackOffPolicy.Exception)] |
| | [InlineData(ExponentialBackOffPolicy.UnsuccessfulResponse503)] |
| | [InlineData(ExponentialBackOffPolicy.Exception | ExponentialBackOffPolicy.UnsuccessfulResponse503)] |
| | public async Task SignBlob_OtherThanRecommendedRetryPolicy(ExponentialBackOffPolicy policy) |
| | { |
| | var initializer = GetInitializerForSignBlob(); |
| | var mockFactory = initializer.HttpClientFactory as MockHttpClientFactory; |
| |
|
| | initializer.DefaultExponentialBackOffPolicy = policy; |
| | var credential = new ComputeCredential(initializer); |
| |
|
| | await credential.SignBlobAsync(Encoding.ASCII.GetBytes("ignored")); |
| |
|
| | |
| | |
| | |
| | |
| | Assert.Equal(3, mockFactory.AllCreateHttpClientArgs.Count()); |
| | var signBlobArgs = mockFactory.AllCreateHttpClientArgs.Last(); |
| |
|
| | |
| | Assert.Equal(2, signBlobArgs.Initializers.Count()); |
| | Assert.Contains(signBlobArgs.Initializers, initializer => initializer is ComputeCredential); |
| | Assert.Contains(signBlobArgs.Initializers, initializer => initializer is ExponentialBackOffInitializer && initializer != GoogleAuthConsts.IamSignBlobEndpointRecommendedRetry); |
| | } |
| |
|
| | [Fact] |
| | public async Task SignBlobAsync() |
| | { |
| | var initializer = GetInitializerForSignBlob(); |
| |
|
| | var credential = new ComputeCredential(initializer); |
| |
|
| | var signature = await credential.SignBlobAsync(Encoding.ASCII.GetBytes("ignored")); |
| | Assert.Equal("Zm9v", signature); |
| | } |
| |
|
| | private ComputeCredential.Initializer GetInitializerForSignBlob() |
| | { |
| | var clock = new MockClock(new DateTime(2020, 5, 21, 9, 20, 0, 0, DateTimeKind.Utc)); |
| | var response = NewtonsoftJsonSerializer.Instance.Serialize(new { keyId = "1", signedBlob = "Zm9v" }); |
| | var fakeAccessToken = "fake_access_token"; |
| | var fakeServiceAccountEmail = "fake-service-account@fake-instance.com"; |
| | var fakeUniverseDomain = "fake.universe.domain.com"; |
| |
|
| | var messageHandler = new DelegatedMessageHandler(FetchServiceAccountId, FetchOAuthToken, SignBlob); |
| | var initializer = new ComputeCredential.Initializer("http://will.be.ignored", "http://will.be.ignored") |
| | { |
| | HttpClientFactory = new MockHttpClientFactory(messageHandler), |
| | Clock = clock, |
| | UniverseDomain = fakeUniverseDomain, |
| | }; |
| |
|
| | return initializer; |
| |
|
| | Task<HttpResponseMessage> FetchServiceAccountId(HttpRequestMessage request) |
| | { |
| | Assert.Null(request.Headers.Authorization); |
| | return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) |
| | { |
| | Content = new StringContent(fakeServiceAccountEmail) |
| | }); |
| | } |
| |
|
| | Task<HttpResponseMessage> FetchOAuthToken(HttpRequestMessage request) |
| | { |
| | Assert.Null(request.Headers.Authorization); |
| | |
| | Assert.Equal($"http://will.be.ignored/?scopes={GoogleAuthConsts.IamScope}", request.RequestUri.ToString()); |
| | return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) |
| | { |
| | Content = new StringContent(NewtonsoftJsonSerializer.Instance.Serialize(new TokenResponse |
| | { |
| | AccessToken = fakeAccessToken, |
| | ExpiresInSeconds = 24 * 60 * 60, |
| | IssuedUtc = clock.UtcNow |
| | })) |
| | }); |
| | } |
| |
|
| | Task<HttpResponseMessage> SignBlob(HttpRequestMessage request) |
| | { |
| | Assert.Equal(string.Format(GoogleAuthConsts.IamSignEndpointFormatString, fakeUniverseDomain, fakeServiceAccountEmail), request.RequestUri.ToString()); |
| | Assert.Equal(fakeAccessToken, request.Headers.Authorization.Parameter); |
| | return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) |
| | { |
| | Content = new StringContent(response) |
| | }); |
| | } |
| | } |
| | } |
| | } |
| |
|