Index: Src/GoogleApis.Auth/OAuth2/Credential.cs |
=================================================================== |
--- a/Src/GoogleApis.Auth/OAuth2/Credential.cs |
+++ b/Src/GoogleApis.Auth/OAuth2/Credential.cs |
@@ -15,13 +15,125 @@ |
*/ |
using System; |
-using System.Collections.Generic; |
-using System.Linq; |
-using System.Text; |
+using System.Net; |
+using System.Net.Http; |
+using System.Threading; |
+using System.Threading.Tasks; |
+ |
+using Google.Apis.Auth.OAuth2.Responses; |
+using Google.Apis.Http; |
+using Google.Apis.Logging; |
namespace Google.Apis.Auth.OAuth2 |
{ |
- public class Credential |
+ /// <summary> |
+ /// 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. |
+ /// </summary> |
+ public class Credential : IHttpExecuteInterceptor, IHttpUnsuccessfulResponseHandler, |
+ IConfigurableHttpClientInitializer |
{ |
+ private static readonly ILogger Logger = ApplicationContext.Logger.ForType<Credential>(); |
+ |
+ private TokenResponse token; |
+ private object lockObject = new object(); |
+ |
+ public TokenResponse Token |
+ { |
+ get |
+ { |
+ lock (lockObject) |
+ { |
+ return token; |
+ } |
+ } |
+ private set |
+ { |
+ lock (lockObject) |
+ { |
+ token = value; |
+ } |
+ } |
+ } |
+ |
+ private readonly IAuthorizationCodeFlow flow; |
+ private readonly string userId; |
+ |
+ /// <summary>Constructs a new credential instance.</summary> |
+ /// <param name="flow">Authorization code flow</param> |
+ /// <param name="userId">User identifier</param> |
+ /// <param name="token">An initial token for the user</param> |
+ public Credential(IAuthorizationCodeFlow flow, string userId, TokenResponse token) |
+ { |
+ this.flow = flow; |
+ this.userId = userId; |
+ this.token = token; |
+ } |
+ |
+ /// <summary> |
+ /// Default implementation is to try to refresh the access token if there is no access token or if we are 1 |
+ /// minute away from expiration. If token server is unavailable, it will try to use the access token even if |
+ /// has expired. If successful, it will call <seealso cref="IAccessMethod.Intercept"/>. |
+ /// </summary> |
+ public async Task InterceptAsync(HttpRequestMessage request, CancellationToken taskCancellationToken) |
+ { |
+ if (Token.IsExpired(flow.Clock)) |
+ { |
+ if (!await RefreshTokenAsync(taskCancellationToken).ConfigureAwait(false)) |
+ { |
+ throw new InvalidOperationException("The access token is expired but we can't refresh it"); |
+ } |
+ } |
+ |
+ flow.AccessMethod.Intercept(request, Token.AccessToken); |
+ } |
+ |
+ /// <summary> |
+ /// Refreshes the token by calling to <seealso cref="IAuthorizationCodeFlow.RefreshTokenAsync"/>. Then it |
+ /// updates the <see cref="TokenResponse"/> with the new token instance. |
+ /// </summary> |
+ /// <param name="taskCancellationToken">Cancellation token to cancel an operation</param> |
+ /// <returns><c>true</c> if the token was refreshed</returns> |
+ private async Task<bool> RefreshTokenAsync(CancellationToken taskCancellationToken) |
+ { |
+ if (Token.RefreshToken == null) |
+ { |
+ Logger.Warning("Refresh token is null, can't refresh the token!"); |
+ return false; |
+ } |
+ |
+ // It's possible that two concurrent calls will be made to refresh the token, in that case the last one |
+ // will win. |
+ var newToken = await flow.RefreshTokenAsync(userId, Token.RefreshToken, taskCancellationToken) |
+ .ConfigureAwait(false); |
+ |
+ Logger.Info("Access token was refreshed"); |
+ |
+ if (newToken.RefreshToken == null) |
+ { |
+ newToken.RefreshToken = Token.RefreshToken; |
+ } |
+ |
+ Token = newToken; |
+ return true; |
+ } |
+ |
+ public async Task<bool> HandleResponseAsync(HandleUnsuccessfulResponseArgs args) |
+ { |
+ // TODO(peleyal): check WWW-Authenticate header |
+ if (args.Response.StatusCode == HttpStatusCode.Unauthorized) |
+ { |
+ return !Object.Equals(Token.AccessToken, flow.AccessMethod.GetAccessToken(args.Request)) |
+ || await RefreshTokenAsync(args.CancellationToken).ConfigureAwait(false); |
+ } |
+ |
+ return false; |
+ } |
+ |
+ public void Initialize(ConfigurableHttpClient httpClient) |
+ { |
+ httpClient.MessageHandler.ExecuteInterceptors.Add(this); |
+ httpClient.MessageHandler.UnsuccessfulResponseHandlers.Add(this); |
+ } |
} |
} |