[tomboy] Implement proper OAuth callback and support for 1.0a (#591455, #594046)
- From: Sanford Armstrong <sharm src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [tomboy] Implement proper OAuth callback and support for 1.0a (#591455, #594046)
- Date: Tue, 8 Sep 2009 00:07:04 +0000 (UTC)
commit 48124fecbe25ceeef98ffe837a28fbcfd072a1a7
Author: Sandy Armstrong <sanfordarmstrong gmail com>
Date: Mon Sep 7 17:03:32 2009 -0700
Implement proper OAuth callback and support for 1.0a (#591455, #594046)
Use an HttpListener to set up a local callback URL, which automatically
triggers token authorization (removing one extra user click).
Add oauth_verifier support, which should make 1.0a servers work, but
so far it is untested with any servers that support OAuth 1.0a.
Tomboy/Addins/WebSyncService/Api/OAuth.cs | 8 +-
Tomboy/Addins/WebSyncService/OAuth/Base.cs | 16 ++-
.../WebSyncService/WebSyncPreferencesWidget.cs | 109 +++++++++++++++-----
3 files changed, 101 insertions(+), 32 deletions(-)
---
diff --git a/Tomboy/Addins/WebSyncService/Api/OAuth.cs b/Tomboy/Addins/WebSyncService/Api/OAuth.cs
index f158243..781886e 100644
--- a/Tomboy/Addins/WebSyncService/Api/OAuth.cs
+++ b/Tomboy/Addins/WebSyncService/Api/OAuth.cs
@@ -60,7 +60,7 @@ namespace Tomboy.WebSync.Api
if (!string.IsNullOrEmpty (qs ["oauth_token"])) {
Token = qs ["oauth_token"];
TokenSecret = qs ["oauth_token_secret"];
- var link = string.Format ("{0}?oauth_token={1}&oauth_callback={2}", AuthorizeLocation, qs ["oauth_token"], "http://www.google.com");
+ var link = string.Format ("{0}?oauth_token={1}&oauth_callback={2}", AuthorizeLocation, qs ["oauth_token"], HttpUtility.UrlEncode (CallbackUrl));
Logger.Debug ("Response from request for auth url: {0}", response);
return link;
}
@@ -147,6 +147,10 @@ namespace Tomboy.WebSync.Api
public string AccessTokenBaseUrl { get; set; }
public string Realm { get; set; }
+
+ public string CallbackUrl { get; set; }
+
+ public string Verifier { get; set; }
#endregion
#region Private Methods
@@ -188,7 +192,7 @@ namespace Tomboy.WebSync.Api
var outUrl = string.Empty;
List<IQueryParameter<string>> parameters = null;
- var sig = GenerateSignature (uri, ConsumerKey, ConsumerSecret, Token, TokenSecret, method,
+ var sig = GenerateSignature (uri, ConsumerKey, ConsumerSecret, Token, TokenSecret, Verifier, method,
timeStamp, nonce, out outUrl, out parameters);
if (Debugging)
diff --git a/Tomboy/Addins/WebSyncService/OAuth/Base.cs b/Tomboy/Addins/WebSyncService/OAuth/Base.cs
index 90c0030..e24ec57 100644
--- a/Tomboy/Addins/WebSyncService/OAuth/Base.cs
+++ b/Tomboy/Addins/WebSyncService/OAuth/Base.cs
@@ -76,6 +76,7 @@ namespace OAuth
private const string OAuthNonceKey = "oauth_nonce";
private const string OAuthTokenKey = "oauth_token";
private const string OAuthTokenSecretKey = "oauth_token_secret";
+ private const string OAuthVerifierKey = "oauth_verifier";
private const string HMACSHA1SignatureType = "HMAC-SHA1";
private const string PlainTextSignatureType = "PLAINTEXT";
@@ -153,10 +154,11 @@ namespace OAuth
/// <param name="consumerKey">The consumer key.</param>
/// <param name="token">The token, if available. If not available pass null or an empty string.</param>
/// <param name="tokenSecret">The token secret, if available. If not available pass null or an empty string.</param>
+ /// <param name="verifier">The callback verifier, if available. If not available pass null or an empty string.</param>
/// <param name="httpMethod">The HTTP method used. Must be a valid HTTP method verb (POST,GET,PUT, etc)</param>
/// <param name="signatureType">The signature type. To use the default values use <see cref="SignatureType">SignatureType</see>.</param>
/// <returns>The signature base.</returns>
- private string GenerateSignatureBase (Uri url, string consumerKey, string token, string tokenSecret,
+ private string GenerateSignatureBase (Uri url, string consumerKey, string token, string tokenSecret, string verifier,
RequestMethod method, TimeSpan timeStamp, string nonce, SignatureType signatureType, out string normalizedUrl,
out List<IQueryParameter<string>> parameters)
{
@@ -164,6 +166,7 @@ namespace OAuth
token = token ?? string.Empty;
tokenSecret = tokenSecret ?? string.Empty;
+ verifier = verifier ?? String.Empty;
if (consumerKey == null) throw new ArgumentNullException ("consumerKey");
@@ -192,6 +195,7 @@ namespace OAuth
}).ToList ();
if (!string.IsNullOrEmpty (token)) parameters.Add (new QueryParameter<string> (OAuthTokenKey, token, s => string.IsNullOrEmpty (s)));
+ if (!string.IsNullOrEmpty (verifier)) parameters.Add (new QueryParameter<string> (OAuthVerifierKey, verifier, s => string.IsNullOrEmpty (s)));
log.LogDebug ("Normalizing URL for signature.");
@@ -243,14 +247,15 @@ namespace OAuth
/// <param name="consumerSecret">The consumer seceret.</param>
/// <param name="token">The token, if available. If not available pass null or an empty string.</param>
/// <param name="tokenSecret">The token secret, if available. If not, pass null or an empty string.</param>
+ /// <param name="verifier">The callback verifier, if available. If not, pass null or an empty string.</param>
/// <param name="httpMethod">The HTTP method used. Must be valid HTTP method verb (POST, GET, PUT, etc).</param>
/// <returns>A Base64 string of the hash value.</returns>
protected string GenerateSignature (Uri url, string consumerKey, string consumerSecret, string token,
- string tokenSecret, RequestMethod method, TimeSpan timeStamp, string nonce, out string normalizedUrl,
+ string tokenSecret, string verifier, RequestMethod method, TimeSpan timeStamp, string nonce, out string normalizedUrl,
out List<IQueryParameter<string>> parameters)
{
log.LogDebug ("Generating signature using HMAC-SHA1 algorithm.");
- return GenerateSignature (url, consumerKey, consumerSecret, token, tokenSecret, method, timeStamp, nonce,
+ return GenerateSignature (url, consumerKey, consumerSecret, token, tokenSecret, verifier, method, timeStamp, nonce,
SignatureType.HMACSHA1, out normalizedUrl, out parameters);
}
@@ -262,11 +267,12 @@ namespace OAuth
/// <param name="consumerSecret">The consumer seceret.</param>
/// <param name="token">The token, if available. If not available pass null or an empty string.</param>
/// <param name="tokenSecret">The token secret, if available. If not, pass null or an empty string.</param>
+ /// <param name="verifier">The callback verifier, if available. If not, pass null or an empty string.</param>
/// <param name="httpMethod">The HTTP method used. Must be a valid HTTP method verb (POST,GET,PUT, etc).</param>
/// <param name="signatureType">The type of signature to use.</param>
/// <returns>A Base64 string of the hash value.</returns>
private string GenerateSignature (Uri url, string consumerKey, string consumerSecret, string token,
- string tokenSecret, RequestMethod method, TimeSpan timeStamp, string nonce, SignatureType signatureType,
+ string tokenSecret, string verifier, RequestMethod method, TimeSpan timeStamp, string nonce, SignatureType signatureType,
out string normalizedUrl, out List<IQueryParameter<string>> parameters)
{
log.LogDebug ("Generating signature using signature type {0}", signatureType);
@@ -281,7 +287,7 @@ namespace OAuth
log.LogDebug ("Plaintext encoding signature {0} of consumer secret and token secret.", signature);
return signature;
case SignatureType.HMACSHA1:
- string signatureBase = GenerateSignatureBase (url, consumerKey, token, tokenSecret, method,
+ string signatureBase = GenerateSignatureBase (url, consumerKey, token, tokenSecret, verifier, method,
timeStamp, nonce, SignatureType.HMACSHA1, out normalizedUrl, out parameters);
var hmacsha1 = new HMACSHA1 ();
diff --git a/Tomboy/Addins/WebSyncService/WebSyncPreferencesWidget.cs b/Tomboy/Addins/WebSyncService/WebSyncPreferencesWidget.cs
index 6cff772..a99c2cc 100644
--- a/Tomboy/Addins/WebSyncService/WebSyncPreferencesWidget.cs
+++ b/Tomboy/Addins/WebSyncService/WebSyncPreferencesWidget.cs
@@ -24,6 +24,8 @@
//
using System;
+using System.Net;
+using System.Web;
using Mono.Unix;
using Tomboy.WebSync.Api;
@@ -34,8 +36,11 @@ namespace Tomboy.WebSync
{
private Gtk.Entry serverEntry;
private Gtk.Button authButton;
- private bool authReqested;
private Api.OAuth oauth;
+ private HttpListener listener;
+
+ private const string callbackHtmlTemplate =
+ @"<html><head><title>{0}</title></head><body><div><h1>{0}</h1>{1}</div></body></html>";
public WebSyncPreferencesWidget (Api.OAuth oauth, string server) : base (false, 5)
{
@@ -92,6 +97,9 @@ namespace Tomboy.WebSync
private void OnAuthButtonClicked (object sender, EventArgs args)
{
+ if (listener != null && listener.IsListening)
+ listener.Close ();
+
// TODO: Move this
if (Auth == null) {
string rootUri = Server + "/api/1.0";
@@ -114,46 +122,97 @@ namespace Tomboy.WebSync
}
}
- if (!Auth.IsAccessToken && !authReqested) {
+ if (!Auth.IsAccessToken) {
+ listener = new HttpListener ();
+ int portToTry = 8000;
+ string callbackUrl = string.Empty;
+ while (!listener.IsListening && portToTry < 9000) {
+ callbackUrl = String.Format ("http://localhost:{0}/tomboy-web-sync/",
+ portToTry);
+ listener.Prefixes.Add (callbackUrl);
+ try {
+ listener.Start ();
+ Auth.CallbackUrl = callbackUrl;
+ } catch {
+ listener.Prefixes.Clear ();
+ portToTry++;
+ }
+ }
+
+ if (!listener.IsListening) {
+ Logger.Error ("Unable to start HttpListener on any port between 8000-8999");
+ authButton.Label = Catalog.GetString ("Server not responding. Try again later.");
+ oauth = null;
+ return;
+ }
+
string authUrl = string.Empty;
try {
authUrl = Auth.GetAuthorizationUrl ();
} catch (Exception e) {
+ listener.Close ();
Logger.Error ("Failed to get auth URL from " + Server + ". Exception was: " + e.ToString ());
+ // Translators: The web service supporting Tomboy WebSync is not responding as expected
authButton.Label = Catalog.GetString ("Server not responding. Try again later.");
oauth = null;
return;
}
+
+ IAsyncResult result = listener.BeginGetContext (delegate (IAsyncResult localResult) {
+ var context = listener.EndGetContext (localResult);
+ // Assuming if we got here user clicked Allow
+ Logger.Debug ("Context request uri query section: " + context.Request.Url.Query);
+ // oauth_verifier is required in OAuth 1.0a, not 1.0
+ var qs = HttpUtility.ParseQueryString (context.Request.Url.Query);
+ if (!String.IsNullOrEmpty (qs ["oauth_verifier"]))
+ Auth.Verifier = qs ["oauth_verifier"];
+ try {
+ if (!Auth.GetAccessAfterAuthorization ())
+ throw new ApplicationException ("Unknown error getting access token");
+ Logger.Debug ("Successfully authorized web sync");
+ } catch (Exception e) {
+ listener.Close ();
+ Logger.Error ("Failed to authorize web sync, with exception:");
+ Logger.Error (e.ToString ());
+ Gtk.Application.Invoke (delegate {
+ authButton.Label = Catalog.GetString ("Authorization Failed, Try Again");
+ authButton.Sensitive = true;
+ });
+ oauth = null;
+ return;
+ }
+ string htmlResponse =
+ String.Format (callbackHtmlTemplate,
+ // Translators: Title of web page presented to user after they authorized Tomboy for sync
+ Catalog.GetString ("Tomboy Web Authorization Successful"),
+ // Translators: Body of web page presented to user after they authorized Tomboy for sync
+ Catalog.GetString ("Please return to the Tomboy Preferences window and press Save to start synchronizing."));
+ using (var writer = new System.IO.StreamWriter (context.Response.OutputStream))
+ writer.Write (htmlResponse);
+ listener.Close ();
+
+ if (Auth.IsAccessToken) {
+ Gtk.Application.Invoke (delegate {
+ authButton.Sensitive = false;
+ authButton.Label = Catalog.GetString ("Connected. Press Save to start synchronizing");
+ });
+ }
+ }, null);
+
+ Logger.Debug ("Listening on {0} for OAuth callback", callbackUrl);
Logger.Debug ("Launching browser to authorize web sync: " + authUrl);
+ authButton.Label = Catalog.GetString ("Authorizing in browser (Press to reset connection)");
try {
Services.NativeApplication.OpenUrl (authUrl, Screen);
- authReqested = true;
- authButton.Label = Catalog.GetString ("Click Here After Authorizing");
} catch (Exception e) {
+ listener.Close ();
Logger.Error ("Exception opening URL: " + e.Message);
+ // Translators: Sometimes a user's default browser is not set, so we recommend setting it and trying again
authButton.Label = Catalog.GetString ("Set the default browser and try again");
+ return;
}
- } else if (!Auth.IsAccessToken && authReqested) {
- authButton.Sensitive = false;
- authButton.Label = Catalog.GetString ("Processing...");
- try {
- if (!Auth.GetAccessAfterAuthorization ())
- throw new ApplicationException ("Unknown error getting access token");
- // TODO: Check Auth.IsAccessToken?
- Logger.Debug ("Successfully authorized web sync");
- authReqested = false;
- } catch (Exception e) {
- Logger.Error ("Failed to authorize web sync, with exception:");
- Logger.Error (e.ToString ());
- authReqested = true;
- authButton.Label = Catalog.GetString ("Authorization Failed, Try Again");
- authButton.Sensitive = true;
- }
- }
-
- if (Auth.IsAccessToken) {
- authButton.Sensitive = false;
- authButton.Label = Catalog.GetString ("Connected. Click Save to start synchronizing");
+ // Translators: The user must take action in their web browser to continue the authorization process
+ authButton.Label = Catalog.GetString ("Authorizing in browser (Press to reset connection)");
}
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]