Implement OAuth token re-authorization

Override the BlogClientBase.Login method with an OAuth2 login flow.
This commit is contained in:
Will Duff 2015-12-14 20:29:11 -08:00
parent 436f2a121b
commit 5919cd1b31
1 changed files with 73 additions and 38 deletions

View File

@ -18,6 +18,8 @@ using Google.Apis.Util.Store;
using Google.Apis.Services;
using Google.Apis.Auth.OAuth2.Flows;
using OpenLiveWriter.BlogClient.Providers;
using Google.Apis.Auth.OAuth2.Responses;
using Google.Apis.Util;
namespace OpenLiveWriter.BlogClient.Clients
{
@ -55,8 +57,7 @@ namespace OpenLiveWriter.BlogClient.Clients
private static IDataStore GetCredentialsDataStoreForBlog(string blogId)
{
// The Google APIs will automatically store the OAuth2 tokens in the given path. We use a unique path per
// blog to support multiple Blogger accounts.
// The Google APIs will automatically store the OAuth2 tokens in the given path.
var folderPath = Path.Combine(ApplicationEnvironment.LocalApplicationDataDirectory, "GoogleBloggerv3");
return new FileDataStore(folderPath, true);
}
@ -88,9 +89,26 @@ namespace OpenLiveWriter.BlogClient.Clients
_clientOptions = clientOptions;
}
private BloggerService GetService()
{
TransientCredentials transientCredentials = Login();
return new BloggerService(new BaseClientService.Initializer()
{
HttpClientInitializer = (UserCredential)transientCredentials.Token
});
}
private bool IsValidToken(TokenResponse token)
{
// If the token is expired but we have a non-null RefreshToken, we can assume the token will be
// automatically refreshed when we query Google Blogger and is therefore valid.
return token != null && (!token.IsExpired(SystemClock.Default) || token.RefreshToken != null);
}
protected override TransientCredentials Login()
{
TransientCredentials transientCredentials = Credentials.TransientCredentials as TransientCredentials;
var transientCredentials = Credentials.TransientCredentials as TransientCredentials ??
new TransientCredentials(Credentials.Username, Credentials.Password, null);
VerifyAndRefreshCredentials(transientCredentials);
return transientCredentials;
}
@ -102,39 +120,64 @@ namespace OpenLiveWriter.BlogClient.Clients
private void VerifyAndRefreshCredentials(TransientCredentials tc)
{
var flowInitializer = new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecretsStream = ClientSecretsStream,
DataStore = GetCredentialsDataStoreForBlog(tc.Username),
Scopes = new List<string>() { BloggerServiceScope, PicasaServiceScope },
};
var flow = new GoogleAuthorizationCodeFlow(flowInitializer);
var userCredential = tc.Token as UserCredential;
var token = userCredential?.Token;
var cancellationTokenSource = new CancellationTokenSource();
// Attempt to load a cached OAuth token.
var loadTokenTask = flow.LoadTokenAsync(tc.Username, cancellationTokenSource.Token);
loadTokenTask.Wait();
if (loadTokenTask.IsCompleted)
if (IsValidToken(token))
{
var token = loadTokenTask.Result;
if (token == null || (token.RefreshToken == null && token.IsExpired(flow.Clock)))
// We already have a valid OAuth token.
return;
}
if (userCredential == null)
{
// Attempt to load a cached OAuth token.
var flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
// The token is invalid, so we need to login again.
if (BlogClientUIContext.SilentModeForCurrentThread)
{
// If we're in silent mode where prompting isn't allowed, throw the verification exception
throw new BlogClientAuthenticationException(String.Empty, String.Empty);
}
ClientSecretsStream = ClientSecretsStream,
DataStore = GetCredentialsDataStoreForBlog(tc.Username),
Scopes = new List<string>() { BloggerServiceScope, PicasaServiceScope },
});
var authorizationTask = GetOAuth2AuthorizationAsync(tc.Username, cancellationTokenSource.Token);
authorizationTask.Wait();
if (authorizationTask.Result?.Token == null)
{
throw new BlogClientOperationCancelledException();
}
var loadTokenTask = flow.LoadTokenAsync(tc.Username, cancellationTokenSource.Token);
loadTokenTask.Wait();
if (loadTokenTask.IsCompleted)
{
// We were able re-create the user credentials from the cache.
userCredential = new UserCredential(flow, tc.Username, loadTokenTask.Result);
token = loadTokenTask.Result;
}
}
if (!IsValidToken(token))
{
// The token is invalid, so we need to login again. This likely includes popping out a new browser window.
if (BlogClientUIContext.SilentModeForCurrentThread)
{
// If we're in silent mode where prompting isn't allowed, throw the verification exception
throw new BlogClientAuthenticationException(String.Empty, String.Empty);
}
// Start an OAuth flow to renew the credentials.
var authorizationTask = GetOAuth2AuthorizationAsync(tc.Username, cancellationTokenSource.Token);
authorizationTask.Wait();
if (authorizationTask.IsCompleted)
{
userCredential = authorizationTask.Result;
token = userCredential?.Token;
}
}
if (!IsValidToken(token))
{
// The token is still invalid after all of our attempts to refresh it. The user did not complete the
// authorization flow, so we interpret that as a cancellation.
throw new BlogClientOperationCancelledException();
}
// Stash the valid user credentials.
tc.Token = userCredential;
}
public void OverrideOptions(IBlogClientOptions newClientOptions)
@ -145,15 +188,7 @@ namespace OpenLiveWriter.BlogClient.Clients
public BlogInfo[] GetUsersBlogs()
{
var cancellationTokenSource = new CancellationTokenSource();
var userCredentialsTask = GetOAuth2AuthorizationAsync(Credentials.Username, cancellationTokenSource.Token);
userCredentialsTask.Wait(cancellationTokenSource.Token);
BloggerService service = new BloggerService(new BaseClientService.Initializer()
{
HttpClientInitializer = userCredentialsTask.Result
});
var listBlogsTask = service.Blogs.ListByUser("self").ExecuteAsync();
var listBlogsTask = GetService().Blogs.ListByUser("self").ExecuteAsync();
listBlogsTask.Wait(cancellationTokenSource.Token);
return listBlogsTask.Result?.Items?.Select(x => new BlogInfo(x.Id, x.Name, x.Url)).ToArray();
}