Ejemplo n.º 1
0
        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);
        }
Ejemplo n.º 2
0
        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);
        }
Ejemplo n.º 3
0
        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);
        }
Ejemplo n.º 5
0
        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);
        }
Ejemplo n.º 6
0
        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);
        }
Ejemplo n.º 7
0
        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);
        }
Ejemplo n.º 9
0
 public void FromSignedToken_ThrowsIfIncorrectPartCount(string jwt)
 {
     Assert.Throws <InvalidJwtException>(
         () => SignedToken <JsonWebSignature.Header, JsonWebSignature.Payload> .FromSignedToken(jwt));
 }
Ejemplo n.º 10
0
 public void FromSignedToken_ThrowsIfEmpty()
 {
     Assert.Throws <ArgumentException>(
         () => SignedToken <JsonWebSignature.Header, JsonWebSignature.Payload> .FromSignedToken(string.Empty));
 }
Ejemplo n.º 11
0
 public void FromSignedToken_ThrowsIfNull()
 {
     Assert.Throws <ArgumentNullException>(
         () => SignedToken <JsonWebSignature.Header, JsonWebSignature.Payload> .FromSignedToken(null));
 }
Ejemplo n.º 12
0
 public void FromSignedToken_ThrowsIfNotValidJson()
 {
     Assert.Throws <JsonReaderException>(
         () => SignedToken <JsonWebSignature.Header, JsonWebSignature.Payload> .FromSignedToken(
             "YmFzZV82NF9oZWFkZXJfbm9fanNvbg==.YmFzZV82NF9wYXlsb2FkX25vX2pzb24=.c2lnbmF0dXJl"));
 }
Ejemplo n.º 13
0
 public void FromSignedToken_ThrowsIfNotBase64()
 {
     Assert.Throws <FormatException>(
         () => SignedToken <JsonWebSignature.Header, JsonWebSignature.Payload> .FromSignedToken(
             "not_base64_header.not_base64_serialized_payload.signature"));
 }
Ejemplo n.º 14
0
        /// <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);
            }
        }