diff --git a/Src/GoogleApis.Auth.DotNet4.Tests/GoogleApis.Auth.DotNet4.Tests.csproj b/Src/GoogleApis.Auth.DotNet4.Tests/GoogleApis.Auth.DotNet4.Tests.csproj
index 86017eebb23..87175f1c7ff 100644
--- a/Src/GoogleApis.Auth.DotNet4.Tests/GoogleApis.Auth.DotNet4.Tests.csproj
+++ b/Src/GoogleApis.Auth.DotNet4.Tests/GoogleApis.Auth.DotNet4.Tests.csproj
@@ -96,8 +96,8 @@
-
-
+
+
diff --git a/Src/GoogleApis.Auth.DotNet4.Tests/OAuth2/DefaultCredentialProvider/DefaultCredentialProviderTests.cs b/Src/GoogleApis.Auth.DotNet4.Tests/OAuth2/DefaultCredentialProviderTests.cs
similarity index 99%
rename from Src/GoogleApis.Auth.DotNet4.Tests/OAuth2/DefaultCredentialProvider/DefaultCredentialProviderTests.cs
rename to Src/GoogleApis.Auth.DotNet4.Tests/OAuth2/DefaultCredentialProviderTests.cs
index bef7c056cd1..1b41741b397 100644
--- a/Src/GoogleApis.Auth.DotNet4.Tests/OAuth2/DefaultCredentialProvider/DefaultCredentialProviderTests.cs
+++ b/Src/GoogleApis.Auth.DotNet4.Tests/OAuth2/DefaultCredentialProviderTests.cs
@@ -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
{
/// A mock for the .
class MockDefaultCredentialProvider : DefaultCredentialProvider
diff --git a/Src/GoogleApis.Auth.DotNet4.Tests/OAuth2/DefaultCredentialProvider/GoogleCredentialTests.cs b/Src/GoogleApis.Auth.DotNet4.Tests/OAuth2/GoogleCredentialTests.cs
similarity index 60%
rename from Src/GoogleApis.Auth.DotNet4.Tests/OAuth2/DefaultCredentialProvider/GoogleCredentialTests.cs
rename to Src/GoogleApis.Auth.DotNet4.Tests/OAuth2/GoogleCredentialTests.cs
index c1476136c21..729c9a44ddc 100644
--- a/Src/GoogleApis.Auth.DotNet4.Tests/OAuth2/DefaultCredentialProvider/GoogleCredentialTests.cs
+++ b/Src/GoogleApis.Auth.DotNet4.Tests/OAuth2/GoogleCredentialTests.cs
@@ -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
{
/// Tests for .
[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"",
@@ -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);
}
+
+ ///
+ /// Creates service account credential from stream, obtains a JWT token
+ /// from the credential and checks the access token is well-formed.
+ ///
+ ///
+ [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(UrlSafeDecode64(parts[0]));
+ Assert.AreEqual("JWT", header.Type);
+ Assert.AreEqual("RS256", header.Algorithm);
+
+ var payload = NewtonsoftJsonSerializer.Instance.Deserialize(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));
+ }
}
}
diff --git a/Src/GoogleApis.Auth.DotNet4/GoogleApis.Auth.DotNet4.csproj b/Src/GoogleApis.Auth.DotNet4/GoogleApis.Auth.DotNet4.csproj
index c8b172c9761..f4a57d02311 100644
--- a/Src/GoogleApis.Auth.DotNet4/GoogleApis.Auth.DotNet4.csproj
+++ b/Src/GoogleApis.Auth.DotNet4/GoogleApis.Auth.DotNet4.csproj
@@ -97,8 +97,6 @@
-
-
diff --git a/Src/GoogleApis.Auth.DotNet4/OAuth2/DefaultCredentialProvider.cs b/Src/GoogleApis.Auth.DotNet4/OAuth2/DefaultCredentialProvider.cs
index 82aae3175da..ca12cdc009b 100644
--- a/Src/GoogleApis.Auth.DotNet4/OAuth2/DefaultCredentialProvider.cs
+++ b/Src/GoogleApis.Auth.DotNet4/OAuth2/DefaultCredentialProvider.cs
@@ -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
{
@@ -142,7 +132,7 @@ private async Task 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.
@@ -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));
@@ -222,7 +212,7 @@ private static UserCredential CreateUserCredentialFromJson(JsonCredentialParamet
return new UserCredential(flow, "ApplicationDefaultCredentials", token);
}
- /// Creates a service account credential from JSON data.
+ /// Creates a from JSON data.
private static ServiceAccountCredential CreateServiceAccountCredentialFromJson(JsonCredentialParameters credentialParameters)
{
if (credentialParameters.Type != JsonCredentialParameters.ServiceAccountCredentialType ||
diff --git a/Src/GoogleApis.Auth.DotNet4/OAuth2/GoogleCredential.cs b/Src/GoogleApis.Auth.DotNet4/OAuth2/GoogleCredential.cs
index 742d0f851e9..8723ea291a7 100644
--- a/Src/GoogleApis.Auth.DotNet4/OAuth2/GoogleCredential.cs
+++ b/Src/GoogleApis.Auth.DotNet4/OAuth2/GoogleCredential.cs
@@ -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
@@ -36,10 +32,20 @@ namespace Google.Apis.Auth.OAuth2
/// See for the credential retrieval logic.
///
///
- public abstract class GoogleCredential : IConfigurableHttpClientInitializer, ITokenAccess
+ public class GoogleCredential : IConfigurableHttpClientInitializer, ITokenAccess
{
+ /// Provider implements the logic for creating the application default credential.
private static DefaultCredentialProvider defaultCredentialProvider = new DefaultCredentialProvider();
+ /// The underlying credential being wrapped by this object.
+ protected readonly ICredential credential;
+
+ /// Creates a new GoogleCredential.
+ internal GoogleCredential(ICredential credential)
+ {
+ this.credential = credential;
+ }
+
///
/// Returns the Application Default Credentials which are ambient credentials that identify and authorize
/// the whole application.
@@ -120,182 +126,62 @@ public virtual GoogleCredential CreateScoped(IEnumerable scopes)
void IConfigurableHttpClientInitializer.Initialize(ConfigurableHttpClient httpClient)
{
- Initialize(httpClient);
+ credential.Initialize(httpClient);
}
#endregion
#region ITokenAccess
- TokenResponse ITokenAccess.Token
- {
- get { return Token; }
- }
-
- Task ITokenAccess.RequestAccessTokenAsync(CancellationToken taskCancellationToken)
+ Task ITokenAccess.GetAccessTokenForRequestAsync(string authUri, CancellationToken cancellationToken)
{
- return RequestAccessTokenAsync(taskCancellationToken);
+ return credential.GetAccessTokenForRequestAsync(authUri, cancellationToken);
}
#endregion
/// Provides access to the underlying credential object
- 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.
-
- /// Initializes a HTTP client.
- protected abstract void Initialize(ConfigurableHttpClient httpClient);
-
- /// Gets the current access token.
- protected abstract TokenResponse Token { get; }
-
- /// Requests refreshing the access token.
- protected abstract Task RequestAccessTokenAsync(CancellationToken taskCancellationToken);
+ internal ICredential UnderlyingCredential { get { return credential; } }
#region Factory methods
- /// Creates a GoogleCredential wrapping a .
- internal static GoogleCredential FromCredential(ComputeCredential credential)
- {
- return new ComputeGoogleCredential(credential);
- }
-
/// Creates a GoogleCredential wrapping a .
internal static GoogleCredential FromCredential(ServiceAccountCredential credential)
{
return new ServiceAccountGoogleCredential(credential);
}
- /// Creates a GoogleCredential wrapping a .
- 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.
-
- /// Wraps ComputeCredential as GoogleCredential.
- 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 RequestAccessTokenAsync(CancellationToken taskCancellationToken)
- {
- return credential.RequestAccessTokenAsync(taskCancellationToken);
- }
-
- #endregion
- }
-
- /// Wraps ServiceAccountCredential as GoogleCredential.
+ ///
+ /// Wraps ServiceAccountCredential as GoogleCredential.
+ /// We need this subclass because wrapping ServiceAccountCredential (unlike other wrapped credential types)
+ /// requires special handling for IsCreateScopedRequired and CreateScoped members.
+ ///
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 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 RequestAccessTokenAsync(CancellationToken taskCancellationToken)
- {
- return credential.RequestAccessTokenAsync(taskCancellationToken);
- }
-
- #endregion
- }
-
- /// Wraps UserCredential as GoogleCredential.
- 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 RequestAccessTokenAsync(CancellationToken taskCancellationToken)
- {
- return credential.RefreshTokenAsync(taskCancellationToken);
+ return new ServiceAccountGoogleCredential(new ServiceAccountCredential(initializer));
}
#endregion
diff --git a/Src/GoogleApis.Auth.DotNet4/OAuth2/ServiceAccountCredential.cs b/Src/GoogleApis.Auth.DotNet4/OAuth2/ServiceAccountCredential.cs
index 889a1673a12..a31183e56fe 100644
--- a/Src/GoogleApis.Auth.DotNet4/OAuth2/ServiceAccountCredential.cs
+++ b/Src/GoogleApis.Auth.DotNet4/OAuth2/ServiceAccountCredential.cs
@@ -41,6 +41,14 @@ namespace Google.Apis.Auth.OAuth2
///
/// Take a look in https://developers.google.com/accounts/docs/OAuth2ServiceAccount for more details.
///
+ ///
+ /// Since version 1.9.3, service account credential also supports JSON Web Token access token scenario.
+ /// In this scenario, instead of sending a signed JWT claim to a token server and exchanging it for
+ /// an access token, a locally signed JWT claim bound to an appropriate URI is used as an access token
+ /// directly.
+ /// See for explanation when JWT access token
+ /// is used and when regular OAuth2 token is used.
+ ///
///
public class ServiceAccountCredential : ServiceCredential
{
@@ -106,6 +114,9 @@ public Initializer FromCertificate(X509Certificate2 certificate)
#region Readonly fields
+ /// Unix epoch as a DateTime
+ protected static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
private readonly string id;
private readonly string user;
private readonly IEnumerable scopes;
@@ -131,8 +142,10 @@ public Initializer FromCertificate(X509Certificate2 certificate)
///
public RSACryptoServiceProvider Key { get { return key; } }
+ /// true if this credential has any scopes associated with it.
+ internal bool HasScopes { get { return scopes != null && scopes.Any(); } }
+
/// Constructs a new service account credential using the given initializer.
- ///
public ServiceAccountCredential(Initializer initializer) : base(initializer)
{
id = initializer.Id.ThrowIfNullOrEmpty("initializer.Id");
@@ -151,25 +164,10 @@ public ServiceAccountCredential(Initializer initializer) : base(initializer)
/// true if a new token was received successfully.
public override async Task RequestAccessTokenAsync(CancellationToken taskCancellationToken)
{
- string serializedHeader = CreateSerializedHeader();
- string serializedPayload = GetSerializedPayload();
-
- StringBuilder assertion = new StringBuilder();
- assertion.Append(UrlSafeBase64Encode(serializedHeader))
- .Append(".")
- .Append(UrlSafeBase64Encode(serializedPayload));
-
- // Sign the header and the payload.
- var hashAlg = new SHA256CryptoServiceProvider();
- byte[] assertionHash = hashAlg.ComputeHash(Encoding.ASCII.GetBytes(assertion.ToString()));
-
- var signature = UrlSafeBase64Encode(key.SignHash(assertionHash, "2.16.840.1.101.3.4.2.1" /* SHA256 OIG */));
- assertion.Append(".").Append(signature);
-
// Create the request.
var request = new GoogleAssertionTokenRequest()
{
- Assertion = assertion.ToString()
+ Assertion = CreateAssertionFromPayload(CreatePayload())
};
Logger.Debug("Request a new access token. Assertion data is: " + request.Assertion);
@@ -180,7 +178,72 @@ public override async Task RequestAccessTokenAsync(CancellationToken taskC
return true;
}
+ ///
+ /// Gets an access token to authorize a request.
+ /// If is set and this credential has no scopes associated
+ /// with it, a locally signed JWT access token for given
+ /// is returned. Otherwise, an OAuth2 access token obtained from token server will be returned.
+ /// A cached token is used if possible and the token is only refreshed once its close to its expiry.
+ ///
+ /// The URI the returned token will grant access to.
+ /// The cancellation token.
+ /// The access token.
+ public override async Task GetAccessTokenForRequestAsync(string authUri = null, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ if (!HasScopes && authUri != null)
+ {
+ // TODO(jtattermusch): support caching of JWT access tokens per authUri, currently a new
+ // JWT access token is created each time, which can hurt performance.
+ return CreateJwtAccessToken(authUri);
+ }
+ return await base.GetAccessTokenForRequestAsync(authUri, cancellationToken);
+ }
+
#endregion
+
+ ///
+ /// Creates a JWT access token than can be used in request headers instead of an OAuth2 token.
+ /// This is achieved by signing a special JWT using this service account's private key.
+ /// The URI for which the access token will be valid.
+ ///
+ private string CreateJwtAccessToken(string authUri)
+ {
+ var issuedDateTime = Clock.UtcNow;
+ var issued = (int)(issuedDateTime - UnixEpoch).TotalSeconds;
+ var payload = new JsonWebSignature.Payload()
+ {
+ Issuer = Id,
+ Subject = Id,
+ Audience = authUri,
+ IssuedAtTimeSeconds = issued,
+ ExpirationTimeSeconds = issued + 3600,
+ };
+
+ return CreateAssertionFromPayload(payload);
+ }
+
+ ///
+ /// Signs JWT token using the private key and returns the serialized assertion.
+ ///
+ /// the JWT payload to sign.
+ private string CreateAssertionFromPayload(JsonWebSignature.Payload payload)
+ {
+ string serializedHeader = CreateSerializedHeader();
+ string serializedPayload = NewtonsoftJsonSerializer.Instance.Serialize(payload);
+
+ StringBuilder assertion = new StringBuilder();
+ assertion.Append(UrlSafeBase64Encode(serializedHeader))
+ .Append(".")
+ .Append(UrlSafeBase64Encode(serializedPayload));
+
+ // Sign the header and the payload.
+ var hashAlg = new SHA256CryptoServiceProvider();
+ byte[] assertionHash = hashAlg.ComputeHash(Encoding.ASCII.GetBytes(assertion.ToString()));
+
+ var signature = UrlSafeBase64Encode(key.SignHash(assertionHash, "2.16.840.1.101.3.4.2.1" /* SHA256 OIG */));
+ assertion.Append(".").Append(signature);
+ return assertion.ToString();
+ }
///
/// Creates a serialized header as specified in
@@ -208,13 +271,13 @@ private static RSAParameters ConvertPKCS8ToRSAParameters(string pkcs8PrivateKey)
}
///
- /// Creates a serialized claim set as specified in
+ /// Creates a claim set as specified in
/// https://developers.google.com/accounts/docs/OAuth2ServiceAccount#formingclaimset.
///
- private string GetSerializedPayload()
+ private GoogleJsonWebSignature.Payload CreatePayload()
{
- var issued = (int)(Clock.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds;
- var payload = new GoogleJsonWebSignature.Payload()
+ var issued = (int)(Clock.UtcNow - UnixEpoch).TotalSeconds;
+ return new GoogleJsonWebSignature.Payload()
{
Issuer = Id,
Audience = TokenServerUrl,
@@ -223,8 +286,6 @@ private string GetSerializedPayload()
Subject = User,
Scope = String.Join(" ", Scopes)
};
-
- return NewtonsoftJsonSerializer.Instance.Serialize(payload);
}
/// Encodes the provided UTF8 string into an URL safe base64 string.
diff --git a/Src/GoogleApis.Auth/GoogleApis.Auth.csproj b/Src/GoogleApis.Auth/GoogleApis.Auth.csproj
index ebf873381c4..af38d6f9ebe 100644
--- a/Src/GoogleApis.Auth/GoogleApis.Auth.csproj
+++ b/Src/GoogleApis.Auth/GoogleApis.Auth.csproj
@@ -72,6 +72,9 @@
+
+
+
diff --git a/Src/GoogleApis.Auth/OAuth2/ICredential.cs b/Src/GoogleApis.Auth/OAuth2/ICredential.cs
new file mode 100644
index 00000000000..a97a434f440
--- /dev/null
+++ b/Src/GoogleApis.Auth/OAuth2/ICredential.cs
@@ -0,0 +1,31 @@
+/*
+Copyright 2015 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.Http;
+
+namespace Google.Apis.Auth.OAuth2
+{
+ ///
+ /// The main interface to represent credential in the client library.
+ /// Service account, User account and Compute credential inherit from this interface
+ /// to provide access token functionality. In addition this interface inherits from
+ /// to be able to hook to http requests.
+ /// More details are available in the specific implementations.
+ ///
+ public interface ICredential : IConfigurableHttpClientInitializer, ITokenAccess
+ {
+ }
+}
diff --git a/Src/GoogleApis.Auth.DotNet4/OAuth2/ITokenAccess.cs b/Src/GoogleApis.Auth/OAuth2/ITokenAccess.cs
similarity index 54%
rename from Src/GoogleApis.Auth.DotNet4/OAuth2/ITokenAccess.cs
rename to Src/GoogleApis.Auth/OAuth2/ITokenAccess.cs
index 6c96eb19a4d..e66b4279745 100644
--- a/Src/GoogleApis.Auth.DotNet4/OAuth2/ITokenAccess.cs
+++ b/Src/GoogleApis.Auth/OAuth2/ITokenAccess.cs
@@ -22,19 +22,24 @@ limitations under the License.
namespace Google.Apis.Auth.OAuth2
{
///
- /// Allows direct control over access token and its retrieval.
+ /// Allows direct retrieval of access tokens to authenticate requests.
/// This is necessary for workflows where you don't want to use
/// to access the API.
/// (e.g. gRPC that implemenents the entire HTTP2 stack internally).
///
public interface ITokenAccess
{
- /// Gets the token response which contains the access token.
- TokenResponse Token { get; }
-
- /// Requests a new token.
- /// Cancellation token to cancel operation.
- /// true if a new token was received successfully.
- Task RequestAccessTokenAsync(CancellationToken taskCancellationToken);
+ ///
+ /// Gets an access token to authorize a request.
+ /// Implementations should handle automatic refreshes of the token
+ /// if they are supported.
+ /// The might be required by some credential types
+ /// (e.g. the JWT access token) while other credential types
+ /// migth just ignore it.
+ ///
+ /// The URI the returned token will grant access to.
+ /// The cancellation token.
+ /// The access token.
+ Task GetAccessTokenForRequestAsync(string authUri = null, CancellationToken cancellationToken = default(CancellationToken));
}
}
diff --git a/Src/GoogleApis.Auth.DotNet4/OAuth2/JsonCredentialParameters.cs b/Src/GoogleApis.Auth/OAuth2/JsonCredentialParameters.cs
similarity index 98%
rename from Src/GoogleApis.Auth.DotNet4/OAuth2/JsonCredentialParameters.cs
rename to Src/GoogleApis.Auth/OAuth2/JsonCredentialParameters.cs
index 0bc2cdb97c9..6c0bf07da73 100644
--- a/Src/GoogleApis.Auth.DotNet4/OAuth2/JsonCredentialParameters.cs
+++ b/Src/GoogleApis.Auth/OAuth2/JsonCredentialParameters.cs
@@ -26,7 +26,7 @@ limitations under the License.
namespace Google.Apis.Auth.OAuth2
{
/// Holder for credential parameters read from JSON credential file. Fields are union of parameters for all supported credential types.
- internal class JsonCredentialParameters
+ public class JsonCredentialParameters
{
/// UserCredential is created by the gcloud sdk tool when the user runs gcloud auth login.
public const string AuthorizedUserCredentialType = "authorized_user";
diff --git a/Src/GoogleApis.Auth/OAuth2/ServiceCredential.cs b/Src/GoogleApis.Auth/OAuth2/ServiceCredential.cs
index c8d67d92731..425ab4069f7 100644
--- a/Src/GoogleApis.Auth/OAuth2/ServiceCredential.cs
+++ b/Src/GoogleApis.Auth/OAuth2/ServiceCredential.cs
@@ -40,8 +40,7 @@ namespace Google.Apis.Auth.OAuth2
/// https://cloud.google.com/compute/docs/authentication.
///
///
- public abstract class ServiceCredential : IHttpExecuteInterceptor, IHttpUnsuccessfulResponseHandler,
- IConfigurableHttpClientInitializer
+ public abstract class ServiceCredential : ICredential, IHttpExecuteInterceptor, IHttpUnsuccessfulResponseHandler
{
protected static readonly ILogger Logger = ApplicationContext.Logger.ForType();
@@ -166,17 +165,8 @@ public void Initialize(ConfigurableHttpClient httpClient)
public async Task InterceptAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
- if (Token == null || Token.IsExpired(Clock))
- {
- Logger.Debug("Token has expired, trying to get a new one.");
- if (!await RequestAccessTokenAsync(cancellationToken).ConfigureAwait(false))
- {
- throw new InvalidOperationException("The access token has expired but we can't refresh it");
- }
- Logger.Info("New access token was received successfully");
- }
-
- AccessMethod.Intercept(request, Token.AccessToken);
+ var accessToken = await GetAccessTokenForRequestAsync(request.RequestUri.ToString(), cancellationToken);
+ AccessMethod.Intercept(request, accessToken);
}
#endregion
@@ -197,6 +187,29 @@ public async Task HandleResponseAsync(HandleUnsuccessfulResponseArgs args)
#endregion
+
+ #region ITokenAccess implementation
+
+ ///
+ /// Gets an access token to authorize a request. If the existing token has expired, try to refresh it first.
+ ///
+ ///
+ public virtual async Task GetAccessTokenForRequestAsync(string authUri = null, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ if (Token == null || Token.IsExpired(Clock))
+ {
+ Logger.Debug("Token has expired, trying to get a new one.");
+ if (!await RequestAccessTokenAsync(cancellationToken).ConfigureAwait(false))
+ {
+ throw new InvalidOperationException("The access token has expired but we can't refresh it");
+ }
+ Logger.Info("New access token was received successfully");
+ }
+ return Token.AccessToken;
+ }
+
+ #endregion
+
/// Requests a new token.
/// Cancellation token to cancel operation.
/// true if a new token was received successfully.
diff --git a/Src/GoogleApis.Auth/OAuth2/UserCredential.cs b/Src/GoogleApis.Auth/OAuth2/UserCredential.cs
index c586e1da1ba..9dbb7487fe3 100644
--- a/Src/GoogleApis.Auth/OAuth2/UserCredential.cs
+++ b/Src/GoogleApis.Auth/OAuth2/UserCredential.cs
@@ -31,8 +31,7 @@ namespace Google.Apis.Auth.OAuth2
/// OAuth 2.0 credential for accessing protected resources using an access token, as well as optionally refreshing
/// the access token when it expires using a refresh token.
///
- public class UserCredential : IHttpExecuteInterceptor, IHttpUnsuccessfulResponseHandler,
- IConfigurableHttpClientInitializer
+ public class UserCredential : ICredential, IHttpExecuteInterceptor, IHttpUnsuccessfulResponseHandler
{
protected static readonly ILogger Logger = ApplicationContext.Logger.ForType();
@@ -93,15 +92,7 @@ public UserCredential(IAuthorizationCodeFlow flow, string userId, TokenResponse
///
public async Task InterceptAsync(HttpRequestMessage request, CancellationToken taskCancellationToken)
{
- if (Token.IsExpired(flow.Clock))
- {
- Logger.Debug("Token has expired, trying to refresh it.");
- if (!await RefreshTokenAsync(taskCancellationToken).ConfigureAwait(false))
- {
- throw new InvalidOperationException("The access token has expired but we can't refresh it");
- }
- }
-
+ var accessToken = await GetAccessTokenForRequestAsync(request.RequestUri.ToString(), taskCancellationToken).ConfigureAwait(false);
flow.AccessMethod.Intercept(request, Token.AccessToken);
}
@@ -133,6 +124,23 @@ public void Initialize(ConfigurableHttpClient httpClient)
#endregion
+ #region ITokenAccess implementation
+
+ public virtual async Task GetAccessTokenForRequestAsync(string authUri = null, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ if (Token.IsExpired(flow.Clock))
+ {
+ Logger.Debug("Token has expired, trying to refresh it.");
+ if (!await RefreshTokenAsync(cancellationToken).ConfigureAwait(false))
+ {
+ throw new InvalidOperationException("The access token has expired but we can't refresh it");
+ }
+ }
+ return token.AccessToken;
+ }
+
+ #endregion
+
///
/// Refreshes the token by calling to
/// .