Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JWT access token support. #572

Merged
merged 16 commits into from
Aug 11, 2015
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="OAuth2\DefaultCredentialProvider\GoogleCredentialTests.cs" />
<Compile Include="OAuth2\DefaultCredentialProvider\DefaultCredentialProviderTests.cs" />
<Compile Include="OAuth2\GoogleCredentialTests.cs" />
<Compile Include="OAuth2\DefaultCredentialProviderTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ limitations under the License.
using Google.Apis.Util;
using Google.Apis.Util.Store;

namespace Google.Apis.Auth.OAuth2.DefaultCredentials
namespace Google.Apis.Auth.OAuth2
{
/// <summary>A mock for the <see cref="Google.Apis.Auth.OAuth2.DefaultCredentialProvider"/>.</summary>
class MockDefaultCredentialProvider : DefaultCredentialProvider
Expand Down Expand Up @@ -182,16 +182,16 @@ public async Task GetDefaultCredential_ServiceAccountCredential_FromEnvironmentV

var credential = await credentialProvider.GetDefaultCredentialAsync();

Assert.IsInstanceOf(typeof(ServiceAccountCredential), credential.UnderlyingCredential);
Assert.IsInstanceOf(typeof(JwtServiceAccountCredential), credential.UnderlyingCredential);
Assert.IsTrue(credential.IsCreateScopedRequired);

var scopes = new[] { "https://www.googleapis.com/auth/cloud-platform" };
var scopedCredential = credential.CreateScoped(scopes);
Assert.AreNotSame(credential, scopedCredential);

Assert.IsInstanceOf(typeof(ServiceAccountCredential), scopedCredential.UnderlyingCredential);
Assert.IsInstanceOf(typeof(JwtServiceAccountCredential), scopedCredential.UnderlyingCredential);
Assert.IsFalse(scopedCredential.IsCreateScopedRequired);
CollectionAssert.AreEqual(scopes, ((ServiceAccountCredential) scopedCredential.UnderlyingCredential).Scopes);
CollectionAssert.AreEqual(scopes, ((JwtServiceAccountCredential) scopedCredential.UnderlyingCredential).Scopes);
}

#endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,17 @@ limitations under the License.
using System.Text;
using System.Threading.Tasks;

using Google.Apis.Json;

using NUnit.Framework;

namespace Google.Apis.Auth.OAuth2.DefaultCredentials
namespace Google.Apis.Auth.OAuth2
{
/// <summary>Tests for <see cref="Google.Apis.Auth.OAuth2.GoogleCredential"/>.</summary>
[TestFixture]
public class GoogleCredentialTests
{
private const string DummyAuthUri = "https://www.googleapis.com/google.some_google_api";
private const string DummyUserCredentialFileContents = @"{
""client_id"": ""CLIENT_ID"",
""client_secret"": ""CLIENT_SECRET"",
Expand Down Expand Up @@ -67,7 +70,40 @@ public void FromStream_ServiceAccountCredential()
{
var stream = new MemoryStream(Encoding.UTF8.GetBytes(DummyServiceAccountCredentialFileContents));
var credential = GoogleCredential.FromStream(stream);
Assert.IsInstanceOf(typeof(JwtServiceAccountCredential), credential.UnderlyingCredential);
Assert.IsTrue(credential.IsCreateScopedRequired);
}

/// <summary>
/// Creates service account credential from stream, obtains a JWT token
/// from the credential and checks the access token is well-formed.
/// </summary>
/// <returns></returns>
[Test]
public async Task FromStream_ServiceAccountCredential_GetJwtAccessToken()
{
var stream = new MemoryStream(Encoding.UTF8.GetBytes(DummyServiceAccountCredentialFileContents));
var credential = GoogleCredential.FromStream(stream);

// Without adding scopes, the credential should be generating JWT scopes.
string accessToken = await (credential as ITokenAccess).GetAccessTokenForRequestAsync(DummyAuthUri);

This comment was marked as spam.

This comment was marked as spam.

var parts = accessToken.Split(new[] {'.'}, 3);

This comment was marked as spam.

This comment was marked as spam.


var header = NewtonsoftJsonSerializer.Instance.Deserialize<JsonWebSignature.Header>(UrlSafeDecode64(parts[0]));
Assert.AreEqual("JWT", header.Type);
Assert.AreEqual("RS256", header.Algorithm);

var payload = NewtonsoftJsonSerializer.Instance.Deserialize<JsonWebSignature.Payload>(UrlSafeDecode64(parts[1]));

Assert.AreEqual("CLIENT_EMAIL", payload.Issuer);
Assert.AreEqual("CLIENT_EMAIL", payload.Subject);
Assert.AreEqual(DummyAuthUri, payload.Audience);
Assert.AreEqual(3600, payload.ExpirationTimeSeconds - payload.IssuedAtTimeSeconds);
}

private string UrlSafeDecode64(string urlSafeBase64)
{
return Encoding.UTF8.GetString(Convert.FromBase64String(urlSafeBase64));
}
}
}
2 changes: 1 addition & 1 deletion Src/GoogleApis.Auth.DotNet4/GoogleApis.Auth.DotNet4.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,12 @@
</ItemGroup>
<ItemGroup>
<Compile Include="OAuth2\JsonCredentialParameters.cs" />
<Compile Include="OAuth2\ITokenAccess.cs" />
<Compile Include="OAuth2\GoogleCredential.cs" />
<Compile Include="OAuth2\DefaultCredentialProvider.cs" />
<Compile Include="OAuth2\GoogleWebAuthorizationBroker.cs" />
<Compile Include="OAuth2\LocalServerCodeReceiver.cs" />
<Compile Include="OAuth2\PromptCodeReceiver.cs" />
<Compile Include="OAuth2\JwtServiceAccountCredential.cs" />
<Compile Include="OAuth2\ServiceAccountCredential.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
Expand Down
16 changes: 3 additions & 13 deletions Src/GoogleApis.Auth.DotNet4/OAuth2/DefaultCredentialProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,13 @@ limitations under the License.
*/

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

using Newtonsoft.Json.Linq;
using Newtonsoft.Json;

using Google.Apis.Auth.OAuth2.Flows;
using Google.Apis.Auth.OAuth2.Responses;
using Google.Apis.Http;
using Google.Apis.Json;
using Google.Apis.Logging;
using Google.Apis.Util;

namespace Google.Apis.Auth.OAuth2
{
Expand Down Expand Up @@ -222,8 +212,8 @@ private static UserCredential CreateUserCredentialFromJson(JsonCredentialParamet
return new UserCredential(flow, "ApplicationDefaultCredentials", token);
}

/// <summary>Creates a service account credential from JSON data.</summary>
private static ServiceAccountCredential CreateServiceAccountCredentialFromJson(JsonCredentialParameters credentialParameters)
/// <summary>Creates a <see cref="JwtServiceAccountCredential"/> from JSON data.</summary>
private static JwtServiceAccountCredential CreateServiceAccountCredentialFromJson(JsonCredentialParameters credentialParameters)
{
if (credentialParameters.Type != JsonCredentialParameters.ServiceAccountCredentialType ||
string.IsNullOrEmpty(credentialParameters.ClientEmail) ||
Expand All @@ -232,7 +222,7 @@ private static ServiceAccountCredential CreateServiceAccountCredentialFromJson(J
throw new InvalidOperationException("JSON data does not represent a valid service account credential.");
}
var initializer = new ServiceAccountCredential.Initializer(credentialParameters.ClientEmail);
return new ServiceAccountCredential(initializer.FromPrivateKey(credentialParameters.PrivateKey));
return new JwtServiceAccountCredential(initializer.FromPrivateKey(credentialParameters.PrivateKey));
}

/// <summary>
Expand Down
162 changes: 31 additions & 131 deletions Src/GoogleApis.Auth.DotNet4/OAuth2/GoogleCredential.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,11 @@ You may obtain a copy of the License at
limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

using Google.Apis.Auth.OAuth2.Responses;
using Google.Apis.Http;

namespace Google.Apis.Auth.OAuth2
Expand All @@ -36,10 +32,24 @@ namespace Google.Apis.Auth.OAuth2
/// See <see cref="GetApplicationDefaultAsync"/> for the credential retrieval logic.
/// </para>
/// </summary>
public abstract class GoogleCredential : IConfigurableHttpClientInitializer, ITokenAccess
public class GoogleCredential : IConfigurableHttpClientInitializer, ITokenAccess
{
private static DefaultCredentialProvider defaultCredentialProvider = new DefaultCredentialProvider();

This comment was marked as spam.

This comment was marked as spam.


private readonly object credential;
private readonly ITokenAccess tokenAccess;
private readonly IConfigurableHttpClientInitializer clientInitializer;

/// <summary>
/// Creates a new <c>GoogleCredential</c>.

This comment was marked as spam.

This comment was marked as spam.

/// </summary>
protected GoogleCredential(object credential, ITokenAccess tokenAccess, IConfigurableHttpClientInitializer clientInitializer)
{
this.credential = credential;
this.tokenAccess = tokenAccess;
this.clientInitializer = clientInitializer;
}

/// <summary>
/// <para>Returns the Application Default Credentials which are ambient credentials that identify and authorize
/// the whole application.</para>
Expand Down Expand Up @@ -120,107 +130,52 @@ public virtual GoogleCredential CreateScoped(IEnumerable<string> scopes)

void IConfigurableHttpClientInitializer.Initialize(ConfigurableHttpClient httpClient)
{
Initialize(httpClient);
clientInitializer.Initialize(httpClient);
}

#endregion

#region ITokenAccess

TokenResponse ITokenAccess.Token
{
get { return Token; }
}

Task<bool> ITokenAccess.RequestAccessTokenAsync(CancellationToken taskCancellationToken)
Task<string> ITokenAccess.GetAccessTokenForRequestAsync(string authUri, CancellationToken cancellationToken)
{
return RequestAccessTokenAsync(taskCancellationToken);
return tokenAccess.GetAccessTokenForRequestAsync(authUri, cancellationToken);
}

#endregion

/// <summary>Provides access to the underlying credential object</summary>
internal abstract object UnderlyingCredential { get; }

// We're explicitly implementing all the interfaces to only expose the members user actually
// needs to see. Because you cannot make explicit interface implementors abstract, they are redirecting
// to the following protected abstract members.

/// <summary>Initializes a HTTP client.</summary>
protected abstract void Initialize(ConfigurableHttpClient httpClient);

/// <summary>Gets the current access token.</summary>
protected abstract TokenResponse Token { get; }

/// <summary>Requests refreshing the access token.</summary>
protected abstract Task<bool> RequestAccessTokenAsync(CancellationToken taskCancellationToken);
internal object UnderlyingCredential { get { return credential; } }

#region Factory methods

/// <summary>Creates a <c>GoogleCredential</c> wrapping a <see cref="ComputeCredential"/>.</summary>
internal static GoogleCredential FromCredential(ComputeCredential credential)
{
return new ComputeGoogleCredential(credential);
return new GoogleCredential(credential, credential, credential);

This comment was marked as spam.

This comment was marked as spam.

}

/// <summary>Creates a <c>GoogleCredential</c> wrapping a <see cref="ServiceAccountCredential"/>.</summary>
internal static GoogleCredential FromCredential(ServiceAccountCredential credential)
internal static GoogleCredential FromCredential(JwtServiceAccountCredential credential)
{
return new ServiceAccountGoogleCredential(credential);
return new JwtServiceAccountGoogleCredential(credential);
}

/// <summary>Creates a <c>GoogleCredential</c> wrapping a <see cref="UserCredential"/>.</summary>
internal static GoogleCredential FromCredential(UserCredential credential)
{
return new UserGoogleCredential(credential);
return new GoogleCredential(credential, credential, credential);
}

#endregion

// TODO(jtattermush): Look into adjusting the API of ServiceAccountCredential, ComputeCredential
// and UserCredential so that they implement ITokenAccess. Then the boilerplate below will go away.

/// <summary>Wraps <c>ComputeCredential</c> as <c>GoogleCredential</c>.</summary>
internal class ComputeGoogleCredential : GoogleCredential
{
private readonly ComputeCredential credential;

public ComputeGoogleCredential(ComputeCredential credential)
{
this.credential = credential;
}

#region GoogleCredential overrides

internal override object UnderlyingCredential
{
get { return credential; }
}

protected override void Initialize(ConfigurableHttpClient httpClient)
{
credential.Initialize(httpClient);
}

protected override TokenResponse Token
{
get { return credential.Token; }
}

protected override Task<bool> RequestAccessTokenAsync(CancellationToken taskCancellationToken)
{
return credential.RequestAccessTokenAsync(taskCancellationToken);
}

#endregion
}

/// <summary>Wraps <c>ServiceAccountCredential</c> as <c>GoogleCredential</c>.</summary>
internal class ServiceAccountGoogleCredential : GoogleCredential
/// <summary>Wraps <c>JwtServiceAccountCredential</c> as <c>GoogleCredential</c>.</summary>
internal class JwtServiceAccountGoogleCredential : GoogleCredential

This comment was marked as spam.

This comment was marked as spam.

{
private readonly ServiceAccountCredential credential;
private readonly JwtServiceAccountCredential credential;

public ServiceAccountGoogleCredential(ServiceAccountCredential credential)
public JwtServiceAccountGoogleCredential(JwtServiceAccountCredential credential)
: base(credential, credential, credential)
{
this.credential = credential;

This comment was marked as spam.

This comment was marked as spam.

}
Expand All @@ -229,73 +184,18 @@ public ServiceAccountGoogleCredential(ServiceAccountCredential credential)

public override bool IsCreateScopedRequired
{
get { return (credential.Scopes == null || credential.Scopes.Count() == 0); }
get { return !credential.HasScopes; }

This comment was marked as spam.

This comment was marked as spam.

}

public override GoogleCredential CreateScoped(IEnumerable<string> scopes)
{
var initializer = new ServiceAccountCredential.Initializer(credential.Id)
var initializer = new JwtServiceAccountCredential.Initializer(credential.Id)
{
User = credential.User,
Key = credential.Key,
Scopes = scopes
};
return GoogleCredential.FromCredential(new ServiceAccountCredential(initializer));
}

internal override object UnderlyingCredential
{
get { return credential; }
}

protected override void Initialize(ConfigurableHttpClient httpClient)
{
credential.Initialize(httpClient);
}

protected override TokenResponse Token
{
get { return credential.Token; }
}

protected override Task<bool> RequestAccessTokenAsync(CancellationToken taskCancellationToken)
{
return credential.RequestAccessTokenAsync(taskCancellationToken);
}

#endregion
}

/// <summary>Wraps <c>UserCredential</c> as <c>GoogleCredential</c>.</summary>
internal class UserGoogleCredential : GoogleCredential
{
private readonly UserCredential credential;

public UserGoogleCredential(UserCredential credential)
{
this.credential = credential;
}

#region GoogleCredential overrides

internal override object UnderlyingCredential
{
get { return credential; }
}

protected override void Initialize(ConfigurableHttpClient httpClient)
{
credential.Initialize(httpClient);
}

protected override TokenResponse Token
{
get { return credential.Token; }
}

protected override Task<bool> RequestAccessTokenAsync(CancellationToken taskCancellationToken)
{
return credential.RefreshTokenAsync(taskCancellationToken);
return GoogleCredential.FromCredential(new JwtServiceAccountCredential(initializer));
}

#endregion
Expand Down
Loading