Skip to content

Commit

Permalink
Merge pull request #572 from jtattermusch/jwt_access_token
Browse files Browse the repository at this point in the history
Add JWT access token support.
  • Loading branch information
jtattermusch committed Aug 11, 2015
2 parents 3b8ec7c + b687352 commit 9405c89
Show file tree
Hide file tree
Showing 13 changed files with 248 additions and 216 deletions.
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
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,41 @@ public void FromStream_ServiceAccountCredential()
{
var stream = new MemoryStream(Encoding.UTF8.GetBytes(DummyServiceAccountCredentialFileContents));
var credential = GoogleCredential.FromStream(stream);
Assert.IsInstanceOf(typeof(ServiceAccountCredential), 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);
var parts = accessToken.Split(new[] {'.'});
Assert.AreEqual(3, parts.Length);

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: 0 additions & 2 deletions Src/GoogleApis.Auth.DotNet4/GoogleApis.Auth.DotNet4.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,6 @@
<Reference Include="System.Xml" />
</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" />
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 @@ -142,7 +132,7 @@ private async Task<GoogleCredential> CreateDefaultCredentialAsync()
if (await ComputeCredential.IsRunningOnComputeEngine().ConfigureAwait(false))
{
Logger.Debug("ComputeEngine check passed. Using ComputeEngine Credentials.");
return GoogleCredential.FromCredential(new ComputeCredential());
return new GoogleCredential(new ComputeCredential());
}

// If everything we tried has failed, throw an exception.
Expand Down Expand Up @@ -185,7 +175,7 @@ private static GoogleCredential CreateDefaultCredentialFromJson(JsonCredentialPa
switch (credentialParameters.Type)
{
case JsonCredentialParameters.AuthorizedUserCredentialType:
return GoogleCredential.FromCredential(CreateUserCredentialFromJson(credentialParameters));
return new GoogleCredential(CreateUserCredentialFromJson(credentialParameters));

case JsonCredentialParameters.ServiceAccountCredentialType:
return GoogleCredential.FromCredential(CreateServiceAccountCredentialFromJson(credentialParameters));
Expand Down Expand Up @@ -222,7 +212,7 @@ private static UserCredential CreateUserCredentialFromJson(JsonCredentialParamet
return new UserCredential(flow, "ApplicationDefaultCredentials", token);
}

/// <summary>Creates a service account credential from JSON data.</summary>
/// <summary>Creates a <see cref="ServiceAccountCredential"/> from JSON data.</summary>
private static ServiceAccountCredential CreateServiceAccountCredentialFromJson(JsonCredentialParameters credentialParameters)
{
if (credentialParameters.Type != JsonCredentialParameters.ServiceAccountCredentialType ||
Expand Down
168 changes: 27 additions & 141 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,20 @@ 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
{
/// <summary>Provider implements the logic for creating the application default credential.</summary>
private static DefaultCredentialProvider defaultCredentialProvider = new DefaultCredentialProvider();

/// <summary>The underlying credential being wrapped by this object.</summary>
protected readonly ICredential credential;

/// <summary>Creates a new <c>GoogleCredential</c>.</summary>
internal GoogleCredential(ICredential credential)
{
this.credential = credential;
}

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

void IConfigurableHttpClientInitializer.Initialize(ConfigurableHttpClient httpClient)
{
Initialize(httpClient);
credential.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 credential.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 ICredential 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);
}

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

/// <summary>Creates a <c>GoogleCredential</c> wrapping a <see cref="UserCredential"/>.</summary>
internal static GoogleCredential FromCredential(UserCredential credential)
{
return new UserGoogleCredential(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>
/// <summary>
/// Wraps <c>ServiceAccountCredential</c> as <c>GoogleCredential</c>.
/// We need this subclass because wrapping <c>ServiceAccountCredential</c> (unlike other wrapped credential types)
/// requires special handling for <c>IsCreateScopedRequired</c> and <c>CreateScoped</c> members.
/// </summary>
internal class ServiceAccountGoogleCredential : GoogleCredential
{
private readonly ServiceAccountCredential credential;

public ServiceAccountGoogleCredential(ServiceAccountCredential credential)
: base(credential)
{
this.credential = credential;
}

#region GoogleCredential overrides

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

public override GoogleCredential CreateScoped(IEnumerable<string> scopes)
{
var initializer = new ServiceAccountCredential.Initializer(credential.Id)
var serviceAccountCredential = credential as ServiceAccountCredential;
var initializer = new ServiceAccountCredential.Initializer(serviceAccountCredential.Id)
{
User = credential.User,
Key = credential.Key,
User = serviceAccountCredential.User,
Key = serviceAccountCredential.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 new ServiceAccountGoogleCredential(new ServiceAccountCredential(initializer));
}

#endregion
Expand Down
Loading

0 comments on commit 9405c89

Please sign in to comment.