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 /// .