Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(962)

Unified Diff: Src/GoogleApis.Auth/OAuth2/Credential.cs

Issue 13972043: Issue 351: Reimplement OAuth2 (Step 3 - Tests, Flows and Credential) (Closed) Base URL: https://google-api-dotnet-client.googlecode.com/hg/
Patch Set: minor Created 10 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
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);
+ }
}
}
« no previous file with comments | « Src/GoogleApis.Auth/OAuth2/BearerToken.cs ('k') | Src/GoogleApis.Auth/OAuth2/GoogleAuthorizationCodeFlow.cs » ('j') | no next file with comments »

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b