Skip to content

Commit

Permalink
Merge v127wip branch into master (#1033)
Browse files Browse the repository at this point in the history
* Provide synchronous access to default credentials (#1018)

Fixes #652

* Validate application name (#1019)

Fixes #729.

* Add download method that returns final download status (#1020)

Fixes #982.

* Support range downloads in generated code (#1021)

Fixes #981

* Unregister CancellationToken, and fix exception handling on cancellation (#1023)

Fixes #1022

* Clarify NET Core support (#1027)

Fixes #1025

* Update version v1.26.2 -> v1.27.0 (#1028)

* Support JWT validation (#1026)

Fixes #920

* Real JWT validation integration test (#1032)
  • Loading branch information
chrisdunelm authored Jun 1, 2017
1 parent ba4f999 commit 364ef9d
Show file tree
Hide file tree
Showing 20 changed files with 575 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"language": "csharp",
"description": "C# libraries for Google APIs.",
"releaseVersion": "1.26.2", "comment1": "Version of generated package.",
"currentSupportVersion": "1.26.2", "comment2": "Version of support library upon which to depend.",
"releaseVersion": "1.27.0", "comment1": "Version of generated package.",
"currentSupportVersion": "1.27.0", "comment2": "Version of support library upon which to depend.",
"pclSupportVersion": "1.25.0", "comment3": "Version of PCL support library.",
"net40SupportVersion": "1.10.0", "comment4": "Version of net40 support library."
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

Supported Platforms:
- .NET Framework 4.5+
- NetStandard1.3
- NetStandard1.3, providing .NET Core support.

Legacy platforms:
- .NET Framework 4.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,22 @@ public override string RestPath
/// <summary>Gets the media downloader.</summary>
public Google.Apis.Download.IMediaDownloader MediaDownloader { get; private set; }

/// <summary>Synchronously download the media into the given stream.</summary>
/// <summary>
/// <para>Synchronously download the media into the given stream.</para>
/// <para>Warning: This method hides download errors; use <see cref="DownloadWithStatus"/> instead.</para>
/// </summary>
public virtual void Download(System.IO.Stream stream)
{
MediaDownloader.Download(this.GenerateRequestUri(), stream);
}

/// <summary>Synchronously download the media into the given stream.</summary>
/// <returns>The final status of the download; including whether the download succeeded or failed.</returns>
public virtual Google.Apis.Download.IDownloadProgress DownloadWithStatus(System.IO.Stream stream)
{
return MediaDownloader.Download(this.GenerateRequestUri(), stream);
}

/// <summary>Asynchronously download the media into the given stream.</summary>
public virtual System.Threading.Tasks.Task<Google.Apis.Download.IDownloadProgress> DownloadAsync(System.IO.Stream stream)
{
Expand All @@ -151,6 +161,26 @@ public virtual System.Threading.Tasks.Task<Google.Apis.Download.IDownloadProgres
{
return MediaDownloader.DownloadAsync(this.GenerateRequestUri(), stream, cancellationToken);
}

#if !NET40
/// <summary>Synchronously download a range of the media into the given stream.</summary>
public virtual Google.Apis.Download.IDownloadProgress DownloadRange(System.IO.Stream stream, System.Net.Http.Headers.RangeHeaderValue range)
{
var mediaDownloader = new Google.Apis.Download.MediaDownloader(Service);
mediaDownloader.Range = range;
return mediaDownloader.Download(this.GenerateRequestUri(), stream);
}

/// <summary>Asynchronously download a range of the media into the given stream.</summary>
public virtual System.Threading.Tasks.Task<Google.Apis.Download.IDownloadProgress> DownloadRangeAsync(System.IO.Stream stream,
System.Net.Http.Headers.RangeHeaderValue range,
System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
{
var mediaDownloader = new Google.Apis.Download.MediaDownloader(Service);
mediaDownloader.Range = range;
return mediaDownloader.DownloadAsync(this.GenerateRequestUri(), stream, cancellationToken);
}
#endif
{% endif %}{% endindent %}
}

Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@ The Google API client library for .NET enables access to Google APIs such as Dri

## Supported Frameworks

* .NET Framework 4.5 and 4.6
* .NET Core (via netstandard1.3 support)
* .NET Framework 4.5+
* netstandard1.3, providing .NET Core support

### ASP.NET Core

ASP.NET Core is supported through the `netstandard1.3` framework target. However there are currently no authentication helpers or examples specific to ASP.NET Core, making authentication more difficult to use than on other platforms. This is being tracked in issue [#933](https://github.com/google/google-api-dotnet-client/issues/933).

## Legacy Supported Frameworks

Expand Down
2 changes: 1 addition & 1 deletion Src/Support/CommonProjectProperties.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<!-- common nupkg information -->
<PropertyGroup>
<Version>1.26.2</Version>
<Version>1.27.0</Version>
<Authors>Google Inc.</Authors>
<Copyright>Copyright 2017 Google Inc.</Copyright>
<PackageTags>Google</PackageTags>
Expand Down
155 changes: 155 additions & 0 deletions Src/Support/Google.Apis.Auth.Tests/GoogleJsonWebSignatureTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
Copyright 2017 Google Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

using Google.Apis.Tests.Mocks;
using System;
using System.Threading.Tasks;
using Xunit;

namespace Google.Apis.Auth.Tests
{
public class GoogleJsonWebSignatureTests
{
// From https://www.googleapis.com/oauth2/v3/certs
const string GoogleCertsJson = @"
{
""keys"": [
{
""kty"": ""RSA"",
""alg"": ""RS256"",
""use"": ""sig"",
""kid"": ""3c066add5889b989e9c49803c21fa4b29d1f4ead"",
""n"": ""h3oJdAYVp3eUXPb2SGvCvEgm_KiRfFrL3cMEdT3I-uKBptz5LklVrOflDJurCXjIQF-891MF6JSJjYc9csiJbUNb-jFXkEI9toOa0ynF-Q9KHrUknfn-R2UrhqWMZw0Xe2WTWS12tjEGEa3Kzf7mcrHV_ARW9vv75PhOMa0dPjgerNJyfDrdTMxOjEzOALHJwjXwzsZxxBiox6ZIXKSGGgC99OOiKZvg6erHP7lEADm0Ws9H7lue3dkv50rpaJBPIUJlYKrx82MnxMAy2NAGqrghjQ5nRdB1mBn4F260lEOwcYNu8aGZ7NhD7yjS_xkECamTYwLGe-9PH8bR54aSxQ"",
""e"": ""AQAB""
},
{
""kty"": ""RSA"",
""alg"": ""RS256"",
""use"": ""sig"",
""kid"": ""c9b39c24ed54a2b1aef3e572d4e411fe5ccf697f"",
""n"": ""zQM50VGJeWRq_hLYKM8dZSVWNTVygDoACA7au37sA7tkNoYhGUopS9veabFpt5MB_h-zEAGhnNpjxRvqVvrvHi5KpfzSPUJxMR9K-YQlqObpb31eebxCiAa2ssewCWslZ2BZ2ID839_YBtbaSZymq5aBgjf07PuWUy55piuaTOuDCJOCPGXvBhMTTFcBNC3zDOZXpkbbG6h6mk5LTPyXzJ322cPjotCZXNF3FsWbFDcCtacr8ZSEMcCGFmrawmko7BQ62FDQtOVGNK94xaTTwtjnxZn1sFIeABIoo8N-x4zbSaZVX9PVQUjtVsX4V5hHiZqmIhcR62h8vj9tVDwxqQ"",
""e"": ""AQAB""
},
{
""kty"": ""RSA"",
""alg"": ""RS256"",
""use"": ""sig"",
""kid"": ""f9079a9ea417bb3c4f5be2805fd9d0aa774609d0"",
""n"": ""lYnlj0CJBnwz_7h5MPUCWpdwXfIxo09_ny2nVEhtZHnaIkpyrtVaUtofs62F8rJAgKN21NrGouIEbiV2i0upO9jffYhSKieZKleM8unuyret3o7Vp3Tme68GEh3ZuSqhyKsia28o5zYy3NzT9Ptt5nZjIk0uTShelHsEV_cGJRUBNmcjJxnKkrSXOABd_CW34GuAZewhfgFWibhulbb6zpNogKB6uv2IIIYF8KaIKyvIwttgyDBSTvgxFxrxWZonTTaB0Ktru2KAyvzoAzZdfnMndHcax6p5D3FnrhNtg4WBxsi8lRO6mz2KrvVYu4wMTD0e4gSPFAhqT8Z-fa7Hcw"",
""e"": ""AQAB""
},
{
""kty"": ""RSA"",
""alg"": ""RS256"",
""use"": ""sig"",
""kid"": ""bbd2c788a9aaa01b7b8ba763f8fae7f88ef57002"",
""n"": ""4f6HXWnlVHL58_VeBC4SjmWNelqVpmpGGIeYKKuee-rV1Y-tQXRHSXT-gDHovz6ZslE6f73CCrYvJ-7223o6ZLa1VTB_5sS5rtcqYK2I7VN7H1gvRAw6rWcwruVjQXzruArnjL00ousC8pKXqoXYtlTyYY0T7J_W2nn_imwk7fCRwe48CCMKi6iDJxaJzEc0Gu5tNPzQoFHez-1ohF8DRxFN6IVV6TEVUMpn9OG-BP7aWfSi5WxnND1IcVnLuLDatqltDzRHzZNYnRkLIVlDzz2pEledY281kE5eQZ0vLjGJ2OR-cEd9SBjbh9EMvig3k7-Co6EAuzpIDXBeJDJ21w"",
""e"": ""AQAB""
}
]
}";

// From procedure outlined in https://developers.google.com/identity/protocols/OpenIDConnect
// This JWT is valid only from 2017-05-31 10:23 until 2017-05-31 11:23 (UTC), so is not usable.
const string JwtGoogleSigned =
"eyJhbGciOiJSUzI1NiIsImtpZCI6ImJiZDJjNzg4YTlhYWEwMWI3YjhiYTc2M2Y4ZmFlN2Y4OGVmNTcwMDIifQ.eyJhenAiOiIy" +
"MzM3NzIyODE0MjUtYWIybWNiaXFtdjhraDBtZG5xc3Jrcm9kOTdxazM3aDAuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJ" +
"hdWQiOiIyMzM3NzIyODE0MjUtYWIybWNiaXFtdjhraDBtZG5xc3Jrcm9kOTdxazM3aDAuYXBwcy5nb29nbGV1c2VyY29udGVudC" +
"5jb20iLCJzdWIiOiIxMDkwODEwMjMzMDk1NTcxOTcyMTIiLCJoZCI6Imdvb2dsZS5jb20iLCJlbWFpbCI6ImNocmlzYmFjb25AZ" +
"29vZ2xlLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJhdF9oYXNoIjoiNFJHRTFzVko4WXhqRUtUaGhFZDZpdyIsImlzcyI6" +
"Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbSIsImlhdCI6MTQ5NjIyNjIzMCwiZXhwIjoxNDk2MjI5ODMwfQ.Opw9C1ma5eww" +
"HmLrmT6to41U0Tpt0fN2A3Vw9jqgS72iRFq0SDQ4r182uwIvhSUo5MnifY4JheeHBuRtDpiQYFtnh_JLzxiAkkIGCMkMf_7Pr6Q" +
"MWuNsx1ugSFygppvlC_fSEK3LS5P2nUtRFqtR7kR9T1MJN11G5dGjLZnmerot8jdqUo7w_zxaiG-5KK-5z3xGRtcPGvl-04RUU7" +
"qsktkVV3AgLuC_TYQGuVH59sfInCEuMZzJJ219MA1c03F0tbDXCMPSWwXgNj4OXaV7QdP7X6sE0AVBK9WVByApI-4CTL7U4G40z" +
"yXSOZ4DQWYiYkNf7Hqw9foa87U0sZS6eQ";
// A valid JWT, with a valid signature, but not signed by Google.
// This JWT is valid only from 2017-05-29 19:02 until 2017-05-29 20:02 (UTC), so is not usable.
const string JwtNonGoogleSigned =
"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJmb3J0ZXN0aW5nQGNocmlzYmFjb24tdGVzdGluZy5pYW0uZ3Nlcn" +
"ZpY2VhY2NvdW50LmNvbSIsInN1YiI6ImZvcnRlc3RpbmdAY2hyaXNiYWNvbi10ZXN0aW5nLmlhbS5nc2VydmljZWFjY291bnQuY" +
"29tIiwiYXVkIjoiaHR0cHM6Ly93d3cuZXhhbXBsZS5jb20vIiwiZXhwIjoxNDk2MDg4MTYwLCJpYXQiOjE0OTYwODQ1NjB9.UE0" +
"oO1QC90rQSmjrWouxUcLV-jay9lnzDraPFbqpcoLcZRyXzG7vUSUVqoipzHweyRyR9bazn_bWCuLuTm9sD-UmokDQGBxqpkGwHJ" +
"vjZ9R6B25vlB5Dqk5QJsPz8Cy_N1wsmeFi41Mn0UsVH1OoQmZHmFgwq61QP1nfzVLlz92sRcEgArJEEM3jwxfFEZVJVckeTqpvA" +
"IMAj-lTi0ysjiKnd7OJwG_HnNqF-nWTU7z0JbZm_l6_Zfyp_wra78YIbDY-VmxBjiz32RDugLqilfhH4o--GThjhdlyHZENMtk-" +
"pO3CE8RfNI5fGnmfUgtf6tcdhk2MiA1Quy8BgB_F5Q";

[Fact]
public async Task Validate_Signature()
{
var clockValid1 = new MockClock() { UtcNow = new DateTime(2017, 5, 31, 10, 24, 0, DateTimeKind.Utc) };
Assert.NotNull(await GoogleJsonWebSignature.ValidateInternalAsync(JwtGoogleSigned, clockValid1, false, GoogleCertsJson));

var clockValid2 = new MockClock() { UtcNow = new DateTime(2017, 5, 31, 10, 24, 0, DateTimeKind.Utc) };
var ex = await Assert.ThrowsAsync<InvalidJwtException>(() =>
GoogleJsonWebSignature.ValidateInternalAsync(JwtNonGoogleSigned, clockValid2, false, GoogleCertsJson));
Assert.Equal("JWT invalid: unable to verify signature.", ex.Message);
}

[Fact]
public async Task Validate_Time()
{

var clockInvalid1 = new MockClock() { UtcNow = new DateTime(2017, 5, 31, 10, 22, 0, DateTimeKind.Utc) };
var clockValid1 = new MockClock() { UtcNow = new DateTime(2017, 5, 31, 10, 24, 0, DateTimeKind.Utc) };
var clockValid2 = new MockClock() { UtcNow = new DateTime(2017, 5, 31, 11, 22, 0, DateTimeKind.Utc) };
var clockInvalid2 = new MockClock() { UtcNow = new DateTime(2017, 5, 31, 11, 24, 0, DateTimeKind.Utc) };

Assert.NotNull(await GoogleJsonWebSignature.ValidateInternalAsync(JwtGoogleSigned, clockValid1, false, GoogleCertsJson));
Assert.NotNull(await GoogleJsonWebSignature.ValidateInternalAsync(JwtGoogleSigned, clockValid2, false, GoogleCertsJson));

var ex1 = await Assert.ThrowsAsync<InvalidJwtException>(() =>
GoogleJsonWebSignature.ValidateInternalAsync(JwtGoogleSigned, clockInvalid1, false, GoogleCertsJson));
Assert.Equal("JWT is not yet valid.", ex1.Message);

var ex2 = await Assert.ThrowsAsync<InvalidJwtException>(() =>
GoogleJsonWebSignature.ValidateInternalAsync(JwtGoogleSigned, clockInvalid2, false, GoogleCertsJson));
Assert.Equal("JWT has expired.", ex2.Message);
}

[Fact]
public async Task Validate_BadJwt()
{
// Null JWT
await Assert.ThrowsAsync<ArgumentNullException>(() => GoogleJsonWebSignature.ValidateInternalAsync(null, null, false, GoogleCertsJson));
// Empty JWT
await Assert.ThrowsAsync<ArgumentException>(() => GoogleJsonWebSignature.ValidateInternalAsync("", null, false, GoogleCertsJson));
// Too long JWT
await Assert.ThrowsAsync<InvalidJwtException>(() =>
GoogleJsonWebSignature.ValidateInternalAsync(new string('a', GoogleJsonWebSignature.MaxJwtLength + 1), null, false, GoogleCertsJson));
// JWT with incorrect top-level structure; missing signature
await Assert.ThrowsAsync<InvalidJwtException>(() =>
GoogleJsonWebSignature.ValidateInternalAsync("header.payload", null, false, GoogleCertsJson));
}

[Fact]
public async Task Validate_CertCache()
{
var clock1 = new MockClock() { UtcNow = new DateTime(2017, 5, 31, 11, 24, 0, DateTimeKind.Utc) };
var clock2Cached = new MockClock() { UtcNow = clock1.UtcNow + GoogleJsonWebSignature.CertCacheRefreshInterval - TimeSpan.FromSeconds(1) };
var clock3Uncached = new MockClock() { UtcNow = clock1.UtcNow + GoogleJsonWebSignature.CertCacheRefreshInterval + TimeSpan.FromSeconds(1) };

var rsas1 = await GoogleJsonWebSignature.GetGoogleCertsAsync(clock1, false, GoogleCertsJson);
var rsas2Cached = await GoogleJsonWebSignature.GetGoogleCertsAsync(clock2Cached, false, GoogleCertsJson);
var rsas3Refreshed = await GoogleJsonWebSignature.GetGoogleCertsAsync(clock3Uncached, false, GoogleCertsJson);
var rsas4Forced = await GoogleJsonWebSignature.GetGoogleCertsAsync(clock3Uncached, true, GoogleCertsJson);

Assert.NotNull(rsas1);
Assert.Same(rsas1, rsas2Cached);
Assert.NotSame(rsas1, rsas3Refreshed);
Assert.NotSame(rsas3Refreshed, rsas4Forced);
}
}
}
1 change: 1 addition & 0 deletions Src/Support/Google.Apis.Auth/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ limitations under the License.
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Google.Apis.Auth.Tests,PublicKey=00240000048000009400000006020000002400005253413100040000010001003d69fa08add2ea7341cc102edb2f3a59bb49e7f7c8bf1bd96d494013c194f4d80ee30278f20e08a0b7cb863d6522d8c1c0071dd36748297deefeb99e899e6a80b9ddc490e88ea566d2f7d4f442211f7beb6b2387fb435bfaa3ecfe7afc0184cc46f80a5866e6bb8eb73f64a3964ed82f6a5036b91b1ac93e1f44508b65e51fce")]
[assembly: InternalsVisibleTo("IntegrationTests,PublicKey=00240000048000009400000006020000002400005253413100040000010001003d69fa08add2ea7341cc102edb2f3a59bb49e7f7c8bf1bd96d494013c194f4d80ee30278f20e08a0b7cb863d6522d8c1c0071dd36748297deefeb99e899e6a80b9ddc490e88ea566d2f7d4f442211f7beb6b2387fb435bfaa3ecfe7afc0184cc46f80a5866e6bb8eb73f64a3964ed82f6a5036b91b1ac93e1f44508b65e51fce")]
2 changes: 1 addition & 1 deletion Src/Support/Google.Apis.Auth/Google.Apis.Auth.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ This package includes auth components like user-credential, authorization code f

Supported Platforms:
- .NET Framework 4.5+
- NetStandard1.3
- NetStandard1.3, providing .NET Core support
</Description>
</PropertyGroup>

Expand Down
Loading

0 comments on commit 364ef9d

Please sign in to comment.