Index: Src/GoogleApis.Auth/OAuth2/UserCredential.cs =================================================================== --- a/Src/GoogleApis.Auth/OAuth2/UserCredential.cs +++ b/Src/GoogleApis.Auth/OAuth2/UserCredential.cs @@ -34,12 +34,12 @@ public class UserCredential : IHttpExecuteInterceptor, IHttpUnsuccessfulResponseHandler, IConfigurableHttpClientInitializer { - private static readonly ILogger Logger = ApplicationContext.Logger.ForType(); + protected static readonly ILogger Logger = ApplicationContext.Logger.ForType(); private TokenResponse token; private object lockObject = new object(); - /// Gets the token response which contains the access token. + /// Gets or sets the token response which contains the access token. public TokenResponse Token { get @@ -49,7 +49,7 @@ return token; } } - private set + set { lock (lockObject) { @@ -58,6 +58,18 @@ } } + /// Gets the authorization code flow. + public IAuthorizationCodeFlow Flow + { + get { return flow; } + } + + /// Gets the user identity. + public string UderId + { + get { return userId; } + } + private readonly IAuthorizationCodeFlow flow; private readonly string userId; @@ -72,6 +84,8 @@ this.token = token; } + #region IHttpExecuteInterceptor + /// /// 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 @@ -91,6 +105,34 @@ flow.AccessMethod.Intercept(request, Token.AccessToken); } + #endregion + + #region IHttpUnsuccessfulResponseHandler + + 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; + } + + #endregion + + #region IConfigurableHttpClientInitializer + + public void Initialize(ConfigurableHttpClient httpClient) + { + httpClient.MessageHandler.ExecuteInterceptors.Add(this); + httpClient.MessageHandler.UnsuccessfulResponseHandlers.Add(this); + } + + #endregion + /// /// Refreshes the token by calling to . Then it /// updates the with the new token instance. @@ -121,22 +163,24 @@ return true; } - public async Task HandleResponseAsync(HandleUnsuccessfulResponseArgs args) + /// + /// Asynchronously revokes the token by calling + /// . + /// + /// Cancellation token to cancel an operation. + /// true if the token was revoked successfully. + public async Task RevokeTokenAsync(CancellationToken taskCancellationToken) { - // TODO(peleyal): check WWW-Authenticate header. - if (args.Response.StatusCode == HttpStatusCode.Unauthorized) + if (Token == null) { - return !Object.Equals(Token.AccessToken, flow.AccessMethod.GetAccessToken(args.Request)) - || await RefreshTokenAsync(args.CancellationToken).ConfigureAwait(false); + Logger.Warning("Token is already null, no need to revoke it."); + return false; } - return false; - } - - public void Initialize(ConfigurableHttpClient httpClient) - { - httpClient.MessageHandler.ExecuteInterceptors.Add(this); - httpClient.MessageHandler.UnsuccessfulResponseHandlers.Add(this); + await flow.RevokeTokenAsync(userId, Token.AccessToken, taskCancellationToken).ConfigureAwait(false); + Logger.Info("Access token was revoked successfully"); + // We don't set the token to null, cause we want that the next request (without reauthorizing) will fail). + return true; } } }