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); |
} |
} |
} |