public void FromNonGoogleSigned() { var signedToken = SignedToken <JsonWebSignature.Header, JsonWebSignature.Payload> .FromSignedToken(FakeCertificateCache.JwtNonGoogleSigned); // Just a small test that everything got correctly unpacked. Assert.DoesNotContain("google.com", signedToken.Payload.Issuer); }
public async Task RefreshesOidcToken() { // A little bit after the tokens returned from OidcTokenFakes were issued. var clock = new MockClock(new DateTime(2020, 5, 13, 15, 0, 0, 0, DateTimeKind.Utc)); var messageHandler = new OidcTokenResponseSuccessMessageHandler(); var initializer = new ServiceAccountCredential.Initializer("MyId", "http://will.be.ignored") { Clock = clock, ProjectId = "a_project_id", HttpClientFactory = new MockHttpClientFactory(messageHandler) }; var credential = new ServiceAccountCredential(initializer.FromPrivateKey(PrivateKey)); var oidcToken = await credential.GetOidcTokenAsync(OidcTokenOptions.FromTargetAudience("audience")); var signedToken = SignedToken <Header, Payload> .FromSignedToken(await oidcToken.GetAccessTokenAsync()); Assert.Equal("https://first_call.test", signedToken.Payload.Audience); // Move the clock so that the token expires. clock.UtcNow = clock.UtcNow.AddHours(2); signedToken = SignedToken <Header, Payload> .FromSignedToken(await oidcToken.GetAccessTokenAsync()); Assert.Equal("https://subsequent_calls.test", signedToken.Payload.Audience); // Two calls, because the second time we tried to get the token, the first one had expired. Assert.Equal(2, messageHandler.Calls); }
public async Task FetchesOidcToken() { // A little bit after the tokens returned from OidcTokenFakes were issued. var clock = new MockClock(new DateTime(2020, 5, 13, 15, 0, 0, 0, DateTimeKind.Utc)); var messageHandler = new OidcTokenResponseSuccessMessageHandler(); var initializer = new ServiceAccountCredential.Initializer("MyId", "http://will.be.ignored") { Clock = clock, ProjectId = "a_project_id", HttpClientFactory = new MockHttpClientFactory(messageHandler) }; var credential = new ServiceAccountCredential(initializer.FromPrivateKey(PrivateKey)); // The fake Oidc server returns valid tokens (expired in the real world for safety) // but with a set audience that lets us know if the token was refreshed or not. 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); // Move the clock some but not enough that the token expires. clock.UtcNow = clock.UtcNow.AddMinutes(20); signedToken = SignedToken <Header, Payload> .FromSignedToken(await oidcToken.GetAccessTokenAsync()); Assert.Equal("https://first_call.test", signedToken.Payload.Audience); // Only the first call should have resulted in a request. The second time the token hadn't expired. Assert.Equal(1, messageHandler.Calls); }
public async Task RefreshesOidcToken() { // A little bit after the tokens returned from OidcTokenFakes were issued. 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); // The fake Oidc server returns valid tokens (expired in the real world for safty) // but with a set audience that lets us know if the token was refreshed or not. 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); // Move the clock so that the token expires. clock.UtcNow = clock.UtcNow.AddHours(2); signedToken = SignedToken <Header, Payload> .FromSignedToken(await oidcToken.GetAccessTokenAsync()); Assert.Equal("https://subsequent_calls.test", signedToken.Payload.Audience); // Two calls, because the second time we tried to get the token, the first one had expired. Assert.Equal(2, messageHandler.Calls); }
public void FromGoogleSigned_ToJsonWebSignatureTypes() { // This works, we just don't get the specific fields that we can get with // GoogleJsonWebSignature.Header and GoogleJsonWebSignature.Payload. var signedToken = SignedToken <JsonWebSignature.Header, JsonWebSignature.Payload> .FromSignedToken(FakeCertificateCache.JwtGoogleSigned); // Just a small test that everything got correctly unpacked. Assert.Contains("google.com", signedToken.Payload.Issuer); }
public void FromGoogleSigned() { var signedToken = SignedToken <GoogleJsonWebSignature.Header, GoogleJsonWebSignature.Payload> .FromSignedToken(FakeCertificateCache.JwtGoogleSigned); // Just a small test that everything got correctly unpacked. Assert.Contains("google.com", signedToken.Payload.Issuer); // And that we have some Google specific info on the payload. Assert.NotNull(signedToken.Payload.HostedDomain); }
public void FromNonGoogleSigned_ToGoogleJsonWebSignatureTypes() { // This works, because JSON deserialization will just leave unassigned // any of the Google specific fields since they are not present on the JSON. // Validation of this token as a Google signed token won't work though. var signedToken = SignedToken <GoogleJsonWebSignature.Header, GoogleJsonWebSignature.Payload> .FromSignedToken(FakeCertificateCache.JwtNonGoogleSigned); // Just a small test that everything got correctly unpacked. Assert.DoesNotContain("google.com", signedToken.Payload.Issuer); // But we don't have info for any of the specific Google fields. Assert.Null(signedToken.Payload.Scope); Assert.Null(signedToken.Payload.Prn); Assert.Null(signedToken.Payload.HostedDomain); Assert.Null(signedToken.Payload.Email); Assert.Null(signedToken.Payload.Name); Assert.Null(signedToken.Payload.GivenName); Assert.Null(signedToken.Payload.FamilyName); Assert.Null(signedToken.Payload.Picture); Assert.Null(signedToken.Payload.Locale); }
public async Task FromComputeCredential_FetchesOidcToken() { // A little bit after the tokens returned from OidcTokenFakes were issued. 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 computeCredential = new ComputeCredential(initializer); var googleCredential = GoogleCredential.FromComputeCredential(computeCredential); // The fake Oidc server returns valid tokens (expired in the real world for safty) // but with a set audience that lets us know if the token was refreshed or not. var oidcToken = await googleCredential.GetOidcTokenAsync(OidcTokenOptions.FromTargetAudience("will.be.ignored")); var signedToken = SignedToken <Header, Payload> .FromSignedToken(await oidcToken.GetAccessTokenAsync()); Assert.Equal("https://first_call.test", signedToken.Payload.Audience); }
public void FromSignedToken_ThrowsIfIncorrectPartCount(string jwt) { Assert.Throws <InvalidJwtException>( () => SignedToken <JsonWebSignature.Header, JsonWebSignature.Payload> .FromSignedToken(jwt)); }
public void FromSignedToken_ThrowsIfEmpty() { Assert.Throws <ArgumentException>( () => SignedToken <JsonWebSignature.Header, JsonWebSignature.Payload> .FromSignedToken(string.Empty)); }
public void FromSignedToken_ThrowsIfNull() { Assert.Throws <ArgumentNullException>( () => SignedToken <JsonWebSignature.Header, JsonWebSignature.Payload> .FromSignedToken(null)); }
public void FromSignedToken_ThrowsIfNotValidJson() { Assert.Throws <JsonReaderException>( () => SignedToken <JsonWebSignature.Header, JsonWebSignature.Payload> .FromSignedToken( "YmFzZV82NF9oZWFkZXJfbm9fanNvbg==.YmFzZV82NF9wYXlsb2FkX25vX2pzb24=.c2lnbmF0dXJl")); }
public void FromSignedToken_ThrowsIfNotBase64() { Assert.Throws <FormatException>( () => SignedToken <JsonWebSignature.Header, JsonWebSignature.Payload> .FromSignedToken( "not_base64_header.not_base64_serialized_payload.signature")); }
/// <summary> /// Asynchronously parses a <see cref="TokenResponse"/> instance from the specified <see cref="HttpResponseMessage"/>. /// </summary> /// <param name="response">The http response from which to parse the token.</param> /// <param name="clock">The clock used to set the <see cref="Issued"/> value of the token.</param> /// <param name="logger">The logger used to output messages incase of error.</param> /// <exception cref="TokenResponseException"> /// The response was not successful or there is an error parsing the response into valid <see cref="TokenResponse"/> instance. /// </exception> /// <returns> /// A task containing the <see cref="TokenResponse"/> parsed form the response message. /// </returns> public static async Task <TokenResponse> FromHttpResponseAsync(HttpResponseMessage response, IClock clock, ILogger logger) { var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var typeName = ""; try { if (!response.IsSuccessStatusCode) { typeName = nameof(TokenErrorResponse); var error = NewtonsoftJsonSerializer.Instance.Deserialize <TokenErrorResponse>(content); throw new TokenResponseException(error, response.StatusCode); } TokenResponse newToken; // GCE's metadata server identity endpoint doesn't return a TokenResponse but the raw // id_token, so we build a TokenResponse from that. if (response.RequestMessage?.RequestUri?.AbsoluteUri.StartsWith(GoogleAuthConsts.ComputeOidcTokenUrl) == true) { newToken = new TokenResponse { IdToken = content }; } else { typeName = nameof(TokenResponse); newToken = NewtonsoftJsonSerializer.Instance.Deserialize <TokenResponse>(content); } // We make some modifications to the token before returning, to guarantee consistency // for our code across endpoint usage. // We should set issuance ourselves. newToken.IssuedUtc = clock.UtcNow; // If no access token was specified, then we're probably receiving // and OIDC token for IAP. The IdToken is used for access in that case. newToken.AccessToken ??= newToken.IdToken; // If no expiry is specified, maybe the IdToken has it specified. // We can try and get it from there. if (newToken.ExpiresInSeconds is null && newToken.IdToken != null) { // Unpack the IdToken. var idToken = SignedToken <JsonWebSignature.Header, JsonWebSignature.Payload> .FromSignedToken(newToken.IdToken); // If no expiry was specified in the ID token, there's nothing we can do. if (idToken.Payload.ExpirationTimeSeconds.HasValue) { var expiration = UnixEpoch.AddSeconds(idToken.Payload.ExpirationTimeSeconds.Value); newToken.ExpiresInSeconds = (long)(expiration - newToken.IssuedUtc).TotalSeconds; } } return(newToken); } catch (Newtonsoft.Json.JsonException ex) { logger.Error(ex, $"Exception was caught when deserializing {typeName}. Content is: {content}"); throw new TokenResponseException(new TokenErrorResponse { Error = "Server response does not contain a JSON object. Status code is: " + response.StatusCode }, response.StatusCode); } }