Index: Src/GoogleApis.Auth/OAuth2/UserCredential.cs
===================================================================
new file mode 100644
--- /dev/null
+++ b/Src/GoogleApis.Auth/OAuth2/UserCredential.cs
@@ -0,0 +1,140 @@
+/*
+Copyright 2013 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 System;
+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
+{
+ ///
+ /// 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
+ {
+ private static readonly ILogger Logger = ApplicationContext.Logger.ForType();
+
+ 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;
+
+ /// Constructs a new credential instance.
+ /// Authorization code flow
+ /// User identifier
+ /// An initial token for the user
+ public UserCredential(IAuthorizationCodeFlow flow, string userId, TokenResponse token)
+ {
+ this.flow = flow;
+ this.userId = userId;
+ this.token = token;
+ }
+
+ ///
+ /// 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 .
+ ///
+ 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");
+ }
+ }
+
+ flow.AccessMethod.Intercept(request, Token.AccessToken);
+ }
+
+ ///
+ /// Refreshes the token by calling to . Then it
+ /// updates the with the new token instance.
+ ///
+ /// Cancellation token to cancel an operation
+ /// true if the token was refreshed
+ private async Task 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 successfully");
+
+ if (newToken.RefreshToken == null)
+ {
+ newToken.RefreshToken = Token.RefreshToken;
+ }
+
+ Token = newToken;
+ return true;
+ }
+
+ public async Task 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);
+ }
+ }
+}