[tomboy] Migrate WebSyncService to use OAuth, and add support for latest changes



commit 02584b08a0ec964c83493966a3d11f0918da82c9
Author: Sandy Armstrong <sanfordarmstrong gmail com>
Date:   Mon Jun 29 08:21:20 2009 -0700

    Migrate WebSyncService to use OAuth, and add support for latest changes
    to the REST API.
    
    Import OAuth library code from Bojan Rajkovic's Twarp, with some
    customizations. Includes some extension methods from Mono.Rocks.
    
    Add new IWebConnection abstraction, to greatly simplify Tomboy.WebSync.Api.
    
    Support new Root resource in REST API, and expect complete URLs in
    ResourceReferences objects.
    
    Break WebSyncPreferencesWidget out into its own class, and update it to
    support OAuth. The UI is currently fairly rough, but works.

 Tomboy.mdp                                         |   12 +-
 Tomboy/Addins/WebSyncService/Api/IWebConnection.cs |   41 +++
 Tomboy/Addins/WebSyncService/Api/OAuth.cs          |  283 +++++++++++++++++
 Tomboy/Addins/WebSyncService/Api/RootInfo.cs       |   75 +++++
 Tomboy/Addins/WebSyncService/Api/UserInfo.cs       |   31 +-
 Tomboy/Addins/WebSyncService/Api/WebHelper.cs      |  162 ----------
 Tomboy/Addins/WebSyncService/Makefile.am           |   11 +-
 Tomboy/Addins/WebSyncService/OAuth/Base.cs         |  325 ++++++++++++++++++++
 Tomboy/Addins/WebSyncService/OAuth/Enums.cs        |   49 +++
 Tomboy/Addins/WebSyncService/OAuth/Extensions.cs   |   76 +++++
 .../WebSyncService/OAuth/Mono.Rocks/Check.cs       |   46 +++
 .../WebSyncService/OAuth/Mono.Rocks/IEnumerable.cs |   66 ++++
 .../Addins/WebSyncService/OAuth/QueryParameter.cs  |  204 ++++++++++++
 .../WebSyncService/WebSyncPreferencesWidget.cs     |  169 ++++++++++
 Tomboy/Addins/WebSyncService/WebSyncServer.cs      |   17 +-
 .../Addins/WebSyncService/WebSyncServiceAddin.cs   |  190 +++++++-----
 po/POTFILES.in                                     |    1 +
 17 files changed, 1477 insertions(+), 281 deletions(-)
---
diff --git a/Tomboy.mdp b/Tomboy.mdp
index 0bb6fab..7247e15 100644
--- a/Tomboy.mdp
+++ b/Tomboy.mdp
@@ -155,7 +155,7 @@
     <File name="Tomboy/Addins/WebSyncService/Api/NoteInfo.cs" subtype="Code" buildaction="Compile" />
     <File name="Tomboy/Addins/WebSyncService/Api/UserInfo.cs" subtype="Code" buildaction="Compile" />
     <File name="Tomboy/Addins/WebSyncService/Api/ResourceReference.cs" subtype="Code" buildaction="Compile" />
-    <File name="Tomboy/Addins/WebSyncService/Api/WebHelper.cs" subtype="Code" buildaction="Compile" />
+    <File name="Tomboy/Addins/WebSyncService/Api/IWebConnection.cs" subtype="Code" buildaction="Compile" />
     <File name="Tomboy/Addins/WebSyncService/Hyena.Json/Deserializer.cs" subtype="Code" buildaction="Compile" />
     <File name="Tomboy/Addins/WebSyncService/Hyena.Json/IJsonCollection.cs" subtype="Code" buildaction="Compile" />
     <File name="Tomboy/Addins/WebSyncService/Hyena.Json/JsonArray.cs" subtype="Code" buildaction="Compile" />
@@ -175,6 +175,15 @@
     <File name="Tomboy/Addins/WebSyncService/Tests" subtype="Directory" buildaction="Compile" />
     <File name="Tomboy/Addins/WebSyncService/Tests/NoteConvertTests.cs" subtype="Code" buildaction="Compile" />
     <File name="Tomboy/Addins/WebSyncService/Hyena.Json/README" subtype="Code" buildaction="Nothing" />
+    <File name="Tomboy/Addins/WebSyncService/Api/OAuth.cs" subtype="Code" buildaction="Compile" />
+    <File name="Tomboy/Addins/WebSyncService/WebSyncPreferencesWidget.cs" subtype="Code" buildaction="Compile" />
+    <File name="Tomboy/Addins/WebSyncService/OAuth/Base.cs" subtype="Code" buildaction="Compile" />
+    <File name="Tomboy/Addins/WebSyncService/OAuth/Enums.cs" subtype="Code" buildaction="Compile" />
+    <File name="Tomboy/Addins/WebSyncService/OAuth/Extensions.cs" subtype="Code" buildaction="Compile" />
+    <File name="Tomboy/Addins/WebSyncService/OAuth/QueryParameter.cs" subtype="Code" buildaction="Compile" />
+    <File name="Tomboy/Addins/WebSyncService/OAuth/Mono.Rocks/Check.cs" subtype="Code" buildaction="Compile" />
+    <File name="Tomboy/Addins/WebSyncService/OAuth/Mono.Rocks/IEnumerable.cs" subtype="Code" buildaction="Compile" />
+    <File name="Tomboy/Addins/WebSyncService/Api/RootInfo.cs" subtype="Code" buildaction="Compile" />
   </Contents>
   <References>
     <ProjectReference type="Gac" localcopy="True" refto="gdk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
@@ -187,6 +196,7 @@
     <ProjectReference type="Gac" localcopy="True" refto="pango-sharp, Version=2.10.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
     <ProjectReference type="Gac" localcopy="True" refto="Mono.Cairo, Version=2.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756" />
     <ProjectReference type="Gac" localcopy="True" refto="nunit.framework, Version=2.4.8.0, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77" />
+    <ProjectReference type="Gac" localcopy="True" refto="System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
   </References>
   <MonoDevelop.Autotools.MakefileInfo IntegrationEnabled="True" RelativeMakefileName="./Makefile.am" ExecuteTargetName="run">
     <BuildFilesVar />
diff --git a/Tomboy/Addins/WebSyncService/Api/IWebConnection.cs b/Tomboy/Addins/WebSyncService/Api/IWebConnection.cs
new file mode 100644
index 0000000..dca2956
--- /dev/null
+++ b/Tomboy/Addins/WebSyncService/Api/IWebConnection.cs
@@ -0,0 +1,41 @@
+// Permission is hereby granted, free of charge, to any person obtaining 
+// a copy of this software and associated documentation files (the 
+// "Software"), to deal in the Software without restriction, including 
+// without limitation the rights to use, copy, modify, merge, publish, 
+// distribute, sublicense, and/or sell copies of the Software, and to 
+// permit persons to whom the Software is furnished to do so, subject to 
+// the following conditions: 
+//  
+// The above copyright notice and this permission notice shall be 
+// included in all copies or substantial portions of the Software. 
+//  
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
+// 
+// Copyright (c) 2009 Novell, Inc. (http://www.novell.com) 
+// 
+// Authors: 
+//      Sandy Armstrong <sanfordarmstrong gmail com>
+// 
+
+using System;
+using System.Collections.Generic;
+
+namespace Tomboy.WebSync.Api
+{
+	public interface IWebConnection
+	{
+		string Get (string uri, IDictionary<string, string> queryParameters);
+
+		string Delete (string uri, IDictionary<string, string> queryParameters);
+
+		string Put (string uri, IDictionary<string, string> queryParameters, string putValue);
+
+		string Post (string uri, IDictionary<string, string> queryParameters, string postValue);
+	}
+}
\ No newline at end of file
diff --git a/Tomboy/Addins/WebSyncService/Api/OAuth.cs b/Tomboy/Addins/WebSyncService/Api/OAuth.cs
new file mode 100644
index 0000000..4afd3c0
--- /dev/null
+++ b/Tomboy/Addins/WebSyncService/Api/OAuth.cs
@@ -0,0 +1,283 @@
+// Permission is hereby granted, free of charge, to any person obtaining 
+// a copy of this software and associated documentation files (the 
+// "Software"), to deal in the Software without restriction, including 
+// without limitation the rights to use, copy, modify, merge, publish, 
+// distribute, sublicense, and/or sell copies of the Software, and to 
+// permit persons to whom the Software is furnished to do so, subject to 
+// the following conditions: 
+//  
+// The above copyright notice and this permission notice shall be 
+// included in all copies or substantial portions of the Software. 
+//  
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
+// 
+// Copyright (c) 2009 Novell, Inc. (http://www.novell.com) 
+// 
+// Authors: 
+//      Sandy Armstrong <sanfordarmstrong gmail com>
+// Based on code from:
+//      Bojan Rajkovic <bojanr brandeis edu>
+//      Shannon Whitley <swhitley whitleymedia com>
+//      Eran Sandler <http://eran.sandler.co.il/>
+// 
+
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.IO;
+using System.Text;
+using System.Web;
+
+using OAuth;
+using Mono.Rocks;
+
+namespace Tomboy.WebSync.Api
+{
+	// TODO: Rename to OAuthConnection ?
+	public class OAuth : Base, IWebConnection
+	{
+		#region Constructor
+		public OAuth ()
+		{
+			Debugging = false;
+		}
+		#endregion
+
+		#region Public Authorization Methods
+		public string GetAuthorizationUrl ()
+		{
+			var response = Post (RequestTokenBaseUrl, null, string.Empty);
+
+			if (response.Length > 0) {
+				// Response contains token and token secret.  We only need the token until we're authorized.
+				var qs = HttpUtility.ParseQueryString (response);
+				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";);
+					Logger.Debug ("Response from request for auth url: {0}", response);
+					return link;
+				}
+			}
+
+			Logger.Error ("Asked server for preliminary token, but received nothing.");
+
+			return string.Empty;
+		}
+
+		public bool GetAccessAfterAuthorization ()
+		{
+			if (string.IsNullOrEmpty (Token))
+				throw new Exception ("Token");
+
+			Logger.Debug ("Asking server for access token based on authorization token.");
+
+			var response = Post (AccessTokenBaseUrl, null, string.Empty);
+
+			Logger.Debug ("Received response from server: {0}", response);
+
+			if (response.Length > 0) {
+				//Store the Token and Token Secret
+				var qs = HttpUtility.ParseQueryString (response);
+				if (!string.IsNullOrEmpty (qs ["oauth_token"]))
+					Token = qs ["oauth_token"];
+				if (!string.IsNullOrEmpty (qs ["oauth_token_secret"]))
+					TokenSecret = qs ["oauth_token_secret"];
+				Logger.Debug ("Got access token from server");
+				IsAccessToken = true;
+				return true;
+			} else {
+				Logger.Error ("Failed to get access token from server");
+				return false;
+			}
+		}
+		#endregion
+
+		#region IWebConnection implementation
+		public string Get (string uri, IDictionary<string, string> queryParameters)
+		{
+			return WebRequest (RequestMethod.GET,
+			                   BuildUri (uri, queryParameters),
+			                   null);
+		}
+		
+		public string Delete (string uri, IDictionary<string, string> queryParameters)
+		{
+			return WebRequest (RequestMethod.DELETE,
+			                   BuildUri (uri, queryParameters),
+			                   null);
+		}
+		
+		public string Put (string uri, IDictionary<string, string> queryParameters, string putValue)
+		{
+			return WebRequest (RequestMethod.PUT,
+			                   BuildUri (uri, queryParameters),
+			                   putValue);
+		}
+		
+		public string Post (string uri, IDictionary<string, string> queryParameters, string postValue)
+		{
+			return WebRequest (RequestMethod.POST,
+			                   BuildUri (uri, queryParameters),
+			                   postValue);
+		}
+		#endregion
+
+		#region Public Properties
+		public string Token { get; set; }
+
+		public string TokenSecret { get; set; }
+
+		public bool IsAccessToken { get; set; }
+
+		public string ConsumerKey { get; set; }
+
+		public string ConsumerSecret { get; set; }
+
+		public string AuthorizeLocation { get; set; }
+
+		public string RequestTokenBaseUrl { get; set; }
+
+		public string AccessTokenBaseUrl { get; set; }
+
+		public string Realm { get; set; }
+		#endregion
+
+		#region Private Methods
+//		/// <summary>
+//		/// Submit a web request using OAuth, asynchronously.
+//		/// </summary>
+//		/// <param name="method">GET or POST.</param>
+//		/// <param name="url">The full URL, including the query string.</param>
+//		/// <param name="postData">Data to post (query string format), if POST methods.</param>
+//		/// <param name="callback">The callback to call with the web request data when the asynchronous web request finishes.</param>
+//		/// <returns>The return value of QueueUserWorkItem.</returns>
+//		public bool AsyncWebRequest (RequestMethod method, string url, string postData, Action<string> callback)
+//		{
+//			return ThreadPool.QueueUserWorkItem (new WaitCallback (delegate {
+//				callback (WebRequest (method, url, postData));
+//			}));
+//		}
+
+		/// <summary>
+		/// Submit a web request using OAuth.
+		/// </summary>
+		/// <param name="method">GET or POST.</param>
+		/// <param name="url">The full URL, including the query string.</param>
+		/// <param name="postData">Data to post (query string format), if POST methods.</param>
+		/// <returns>The web server response.</returns>
+		private string WebRequest (RequestMethod method, string url, string postData)
+		{
+			Uri uri = new Uri (url);
+
+			var nonce = GenerateNonce ();
+			var timeStamp = GenerateTimeStamp ();
+
+			Logger.Debug ("Building web request for URL: {0}", url);
+			if (Debugging) {
+				Logger.Debug ("Generated nonce is {0}", nonce);
+				Logger.Debug ("Generated time stamp is {0}", timeStamp);
+			}
+
+			var outUrl = string.Empty;
+			List<IQueryParameter<string>> parameters = null;
+
+			var sig = GenerateSignature (uri, ConsumerKey, ConsumerSecret, Token, TokenSecret, method,
+				timeStamp, nonce, out outUrl, out parameters);
+
+			if (Debugging)
+				Logger.Debug ("Generated signature {0}", sig);
+
+			parameters.Add (new QueryParameter<string> ("oauth_signature",
+			                                            HttpUtility.UrlEncode (sig),
+			                                            s => string.IsNullOrEmpty (s)));
+			parameters.Sort ();
+
+			if (Debugging)
+				Logger.Debug ("Post data: {0}", postData);
+
+			var ret = MakeWebRequest (method, url, parameters, postData);
+
+			if (Debugging)
+				Logger.Debug ("Returned value from web request: {0}", ret);
+
+			return ret;
+		}
+
+		/// <summary>
+		/// Wraps a web request into a convenient package.
+		/// </summary>
+		/// <param name="method">HTTP method of the request.</param>
+		/// <param name="url">Full URL to the web resource.</param>
+		/// <param name="postData">Data to post in query string format.</param>
+		/// <returns>The web server response.</returns>
+		private string MakeWebRequest (RequestMethod method,
+		                               string url,
+		                               List<IQueryParameter<string>> parameters,
+		                               string postData)
+		{
+			var responseData = string.Empty;
+
+			// TODO: Set UserAgent, Timeout, KeepAlive, Proxy?
+			HttpWebRequest webRequest = System.Net.WebRequest.Create (url) as HttpWebRequest;
+			webRequest.Method = method.ToString ();
+			webRequest.ServicePoint.Expect100Continue = false;
+
+			var headerParams =
+				parameters.Implode (",", q => string.Format ("{0}=\"{1}\"", q.Name, q.Value));
+			if (Debugging)
+				Logger.Debug ("Generated auth header params string: " + headerParams);
+			webRequest.Headers.Add ("Authorization",
+			                        String.Format ("OAuth realm=\"{0}\",{1}",
+			                                       Realm, headerParams));
+
+			if (!String.IsNullOrEmpty (postData) &&
+			    (method == RequestMethod.PUT ||
+			     method == RequestMethod.POST)) {
+				webRequest.ContentType = "application/json";
+				// TODO: Error handling?
+				using (var requestWriter = new StreamWriter (webRequest.GetRequestStream ()))
+					requestWriter.Write (postData);
+			}
+
+			using (var responseReader = new StreamReader (webRequest.GetResponse ().GetResponseStream ())) {
+				try {
+					responseData = responseReader.ReadToEnd ();
+				} catch (Exception e) {
+					Logger.Error ("Caught exception. Message: {0}", e.Message);
+					Logger.Error ("Stack trace for previous exception: {0}", e.StackTrace);
+					throw;
+				}
+			}
+
+			if (Debugging)
+				Logger.Debug ("Made web request, got response: {0}", responseData);
+
+			return responseData;
+		}
+
+		private string BuildUri (string baseUri, IDictionary<string, string> queryParameters)
+		{
+			StringBuilder urlBuilder = new StringBuilder (baseUri);	// TODO: Capacity?
+			urlBuilder.Append ("?");
+			if (queryParameters != null) {
+				foreach (var param in queryParameters) {
+					urlBuilder.Append (param.Key);
+					urlBuilder.Append ("=");
+					urlBuilder.Append (param.Value);
+					urlBuilder.Append ("&");
+				}
+			}
+			// Get rid of trailing ? or &
+			urlBuilder.Remove (urlBuilder.Length - 1, 1);
+			return urlBuilder.ToString ();
+		}
+		#endregion
+	}
+}
\ No newline at end of file
diff --git a/Tomboy/Addins/WebSyncService/Api/RootInfo.cs b/Tomboy/Addins/WebSyncService/Api/RootInfo.cs
new file mode 100644
index 0000000..21c962a
--- /dev/null
+++ b/Tomboy/Addins/WebSyncService/Api/RootInfo.cs
@@ -0,0 +1,75 @@
+// Permission is hereby granted, free of charge, to any person obtaining 
+// a copy of this software and associated documentation files (the 
+// "Software"), to deal in the Software without restriction, including 
+// without limitation the rights to use, copy, modify, merge, publish, 
+// distribute, sublicense, and/or sell copies of the Software, and to 
+// permit persons to whom the Software is furnished to do so, subject to 
+// the following conditions: 
+//  
+// The above copyright notice and this permission notice shall be 
+// included in all copies or substantial portions of the Software. 
+//  
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
+// 
+// Copyright (c) 2009 Novell, Inc. (http://www.novell.com) 
+// 
+// Authors: 
+//      Sandy Armstrong <sanfordarmstrong gmail com>
+// 
+
+using System;
+
+namespace Tomboy.WebSync.Api
+{
+	public class RootInfo
+	{
+		#region Public Static Methods
+		
+		public static RootInfo GetRoot (string rootUri, IWebConnection connection)
+		{
+			// TODO: Error-handling in GET and Deserialize
+			string jsonString = connection.Get (rootUri, null);
+			RootInfo root = ParseJson (jsonString);
+			return root;
+		}
+
+		public static RootInfo ParseJson (string jsonString)
+		{
+			Hyena.Json.Deserializer deserializer =
+				new Hyena.Json.Deserializer (jsonString);
+			object obj = deserializer.Deserialize ();
+
+			Hyena.Json.JsonObject jsonObj =
+				obj as Hyena.Json.JsonObject;
+			if (jsonObj == null)
+				throw new ArgumentException ("jsonString does not contain a valid RootInfo representation");
+
+			// TODO: Checks
+			RootInfo root = new RootInfo ();
+			root.ApiVersion = (string) jsonObj ["api-version"];
+
+			Hyena.Json.JsonObject userRefJsonObj =
+				(Hyena.Json.JsonObject) jsonObj ["user-ref"];
+			root.User =
+				ResourceReference.ParseJson (userRefJsonObj);
+
+			return root;
+		}
+
+		#endregion
+
+		#region API Members
+
+		public ResourceReference User { get; private set; }
+
+		public string ApiVersion { get; private set; }
+
+		#endregion
+	}
+}
diff --git a/Tomboy/Addins/WebSyncService/Api/UserInfo.cs b/Tomboy/Addins/WebSyncService/Api/UserInfo.cs
index 4a264ce..3adf1d2 100644
--- a/Tomboy/Addins/WebSyncService/Api/UserInfo.cs
+++ b/Tomboy/Addins/WebSyncService/Api/UserInfo.cs
@@ -32,18 +32,13 @@ namespace Tomboy.WebSync.Api
 	{
 		#region Public Static Methods
 		
-		public static UserInfo GetUser (string serverUrl, string userName, IAuthProvider auth)
+		public static UserInfo GetUser (string userUri, IWebConnection connection)
 		{
-			// TODO: Clean this up
-			string baseUrl = serverUrl + "/api/1.0/";
-			string uri = baseUrl + userName;
-			
 			// TODO: Error-handling in GET and Deserialize
-			WebHelper helper = new WebHelper ();
-			string jsonString = helper.Get (uri, null, auth);
+			string jsonString = connection.Get (userUri, null);
 			UserInfo user = ParseJson (jsonString);
-			user.AuthProvider = auth;
-			user.BaseUrl = baseUrl;
+			user.Connection = connection;
+			user.Uri = userUri;
 			return user;
 		}
 
@@ -60,6 +55,7 @@ namespace Tomboy.WebSync.Api
 
 			// TODO: Checks
 			UserInfo user = new UserInfo ();
+			user.UserName = (string) jsonObj ["user-name"];
 			user.FirstName = (string) jsonObj ["first-name"];
 			user.LastName = (string) jsonObj ["last-name"];
 			
@@ -88,7 +84,9 @@ namespace Tomboy.WebSync.Api
 		#endregion
 
 		#region API Members
-		
+
+		public string UserName { get; private set; }
+
 		public string FirstName { get; private set; }
 
 		public string LastName { get; private set; }
@@ -109,7 +107,6 @@ namespace Tomboy.WebSync.Api
 		public IList<NoteInfo> GetNotes (bool includeContent, int sinceRevision, out int? latestSyncRevision)
 		{
 			// TODO: Error-handling in GET and Deserialize
-			WebHelper helper = new WebHelper ();
 			string jsonString = string.Empty;
 
 			Dictionary<string, string> parameters =
@@ -119,7 +116,7 @@ namespace Tomboy.WebSync.Api
 			if (sinceRevision >= 0)
 				parameters ["since"] = sinceRevision.ToString ();
 			
-			jsonString = helper.Get (BaseUrl + Notes.ApiRef, parameters, AuthProvider);
+			jsonString = Connection.Get (Notes.ApiRef, parameters);
 
 			return ParseJsonNotes (jsonString, out latestSyncRevision);
 		}
@@ -127,13 +124,11 @@ namespace Tomboy.WebSync.Api
 		public int UpdateNotes (IList<NoteInfo> noteUpdates, int expectedNewRevision)
 		{
 			// TODO: Error-handling in PUT, Serialize, and Deserialize
-			WebHelper helper = new WebHelper ();
 
 			string jsonResponseString =
-				helper.PutJson (BaseUrl + Notes.ApiRef,
+				Connection.Put (Notes.ApiRef,
 				                null,
-				                CreateNoteChangesJsonString (noteUpdates, expectedNewRevision),
-				                AuthProvider);
+				                CreateNoteChangesJsonString (noteUpdates, expectedNewRevision));
 
 			// TODO: This response object could be extremely useful
 //			using (System.IO.StreamWriter writer = System.IO.File.CreateText ("/home/sandy/lastPutResp"))
@@ -150,9 +145,9 @@ namespace Tomboy.WebSync.Api
 
 		#region Public Properties
 
-		public IAuthProvider AuthProvider { get; set; }
+		public IWebConnection Connection { get; private set; }
 
-		public string BaseUrl { get; set; }
+		public string Uri { get; private set; }
 
 		#endregion
 
diff --git a/Tomboy/Addins/WebSyncService/Makefile.am b/Tomboy/Addins/WebSyncService/Makefile.am
index 1c90695..ecf8a9e 100644
--- a/Tomboy/Addins/WebSyncService/Makefile.am
+++ b/Tomboy/Addins/WebSyncService/Makefile.am
@@ -15,6 +15,7 @@ ASSEMBLIES = \
 	$(GTKSHARP_LIBS)			\
 	$(LINK_MONO_ADDINS)			\
 	$(NUNIT_LIBS)				\
+	-r:System.Web				\
 	-r:Mono.Posix
 
 #
@@ -24,16 +25,16 @@ ASSEMBLIES = \
 TARGET = WebSyncServiceAddin.dll
 CSFILES = \
 	$(srcdir)/NoteConvert.cs       	\
+	$(srcdir)/WebSyncPreferencesWidget.cs   \
 	$(srcdir)/WebSyncServer.cs          \
 	$(srcdir)/WebSyncServiceAddin.cs	\
 	$(srcdir)/Tests/*.cs            	\
-	$(srcdir)/Api/NoteInfo.cs       	\
-	$(srcdir)/Api/ResourceReference.cs	\
-	$(srcdir)/Api/UserInfo.cs       	\
-	$(srcdir)/Api/WebHelper.cs          \
+	$(srcdir)/Api/*.cs                  \
 	$(srcdir)/Api/Tests/*.cs            \
 	$(srcdir)/Hyena.Json/*.cs           \
-	$(srcdir)/Hyena.Json/Tests/*.cs
+	$(srcdir)/Hyena.Json/Tests/*.cs     \
+	$(srcdir)/OAuth/*.cs                \
+	$(srcdir)/OAuth/Mono.Rocks/*.cs
 RESOURCES = \
 	-resource:$(srcdir)/WebSyncService.addin.xml
 
diff --git a/Tomboy/Addins/WebSyncService/OAuth/Base.cs b/Tomboy/Addins/WebSyncService/OAuth/Base.cs
new file mode 100644
index 0000000..90c0030
--- /dev/null
+++ b/Tomboy/Addins/WebSyncService/OAuth/Base.cs
@@ -0,0 +1,325 @@
+//
+// OAuthBase.cs
+//  
+// Author:
+//       Bojan Rajkovic <bojanr brandeis edu>
+//       Shannon Whitley <swhitley whitleymedia com>
+//       Eran Sandler <http://eran.sandler.co.il/>
+//       Sandy Armstrong <sanfordarmstrong gmail com>
+// 
+// Copyright (c) 2009 Bojan Rajkovic
+// 
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using OAuth;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using System.Web;
+
+namespace OAuth
+{
+	/// <summary>
+	/// Provides a base class for OAuth authentication and signing.
+	/// </summary>
+	public class Base
+	{
+		class LoggerAdapter
+		{
+			public void LogDebug (string format, params object[] objects)
+			{
+				if (Debugging)
+					Tomboy.Logger.Debug (format, objects);
+			}
+	
+			public bool Debugging { get; set; }
+		}
+
+		private readonly LoggerAdapter log = new LoggerAdapter ();
+		private bool debugging;
+
+		public bool Debugging
+		{
+			get { return debugging; }
+			set { debugging = value; log.Debugging = value; }
+		}
+
+		private const string OAuthVersion = "1.0";
+
+		//
+		// List of know and used oauth parameters' names
+		//
+		private const string OAuthConsumerKeyKey = "oauth_consumer_key";
+		private const string OAuthCallbackKey = "oauth_callback";
+		private const string OAuthVersionKey = "oauth_version";
+		private const string OAuthSignatureMethodKey = "oauth_signature_method";
+		private const string OAuthSignatureKey = "oauth_signature";
+		private const string OAuthTimestampKey = "oauth_timestamp";
+		private const string OAuthNonceKey = "oauth_nonce";
+		private const string OAuthTokenKey = "oauth_token";
+		private const string OAuthTokenSecretKey = "oauth_token_secret";
+
+		private const string HMACSHA1SignatureType = "HMAC-SHA1";
+		private const string PlainTextSignatureType = "PLAINTEXT";
+		private const string RSASHA1SignatureType = "RSA-SHA1";
+
+		private Random random = new Random ();
+
+		private string unreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
+
+		/// <summary>
+		/// Helper function to compute a hash value.
+		/// </summary>
+		/// <param name="hashAlgorithm">
+		/// 	The hashing algoirhtm used. If that algorithm needs some initialization, like HMAC and its derivatives,
+		/// 	they should be initialized prior to passing it to this function.
+		/// </param>
+		/// <param name="data">The data to hash.</param>
+		/// <returns>A Base64 string of the hash value.</returns>
+		private string ComputeHash (HashAlgorithm hashAlgorithm, string data)
+		{
+			log.LogDebug ("Computing hash for data {0}", data);
+
+			if (hashAlgorithm == null) throw new ArgumentNullException ("hashAlgorithm");
+			if (string.IsNullOrEmpty (data)) throw new ArgumentNullException ("data");
+
+			byte[] dataBuffer = System.Text.Encoding.ASCII.GetBytes (data);
+			byte[] hashBytes = hashAlgorithm.ComputeHash (dataBuffer);
+
+			return Convert.ToBase64String (hashBytes);
+		}
+
+		/// <summary>
+		/// URL encodes a string using OAuth's encoding scheme (slightly different from HttpUtility's UrlEncode).
+		/// </summary>
+		/// <param name="value">The string to URL encode.</param>
+		/// <returns>An URL encoded string.</returns>
+		private string UrlEncode (string value)
+		{
+			log.LogDebug ("URL encoding value.");
+			var result = new StringBuilder ();
+
+			foreach (char symbol in value) {
+				if (unreservedChars.IndexOf(symbol) != -1) result.Append(symbol);
+				else result.Append('%' + String.Format("{0:X2}", (int)symbol));
+			}
+
+			return result.ToString();
+		}
+
+		/// <summary>
+		/// Internal function to cut out all non oauth query string parameters.
+		/// </summary>
+		/// <param name="parameters">The query string part of the URL.</param>
+		/// <returns>A list of QueryParameter each containing the parameter name and value.</returns>
+		private IEnumerable<IQueryParameter<string>> GetQueryParameters (string parameters)
+		{
+			log.LogDebug ("Creating list of parameters from parameter string {0}", parameters);
+
+			return CreateQueryParametersIterator (parameters);
+		}
+
+		private IEnumerable<IQueryParameter<string>> CreateQueryParametersIterator (string parameters)
+		{
+			if (parameters == null) throw new ArgumentNullException ("parameters");
+			var parameterDictionary = HttpUtility.ParseQueryString (parameters).ToDictionary ();
+
+			foreach (var kvp in parameterDictionary)
+				yield return new QueryParameter<string> (kvp.Key, kvp.Value, s => string.IsNullOrEmpty (s));
+		}
+
+		/// <summary>
+		/// Generate the signature base that is used to produce the signature
+		/// </summary>
+		/// <param name="url">The full URL that needs to be signed including its non OAuth URL parameters.</param>
+		/// <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="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,
+			RequestMethod method, TimeSpan timeStamp, string nonce, SignatureType signatureType, out string normalizedUrl,
+			out List<IQueryParameter<string>> parameters)
+		{
+			log.LogDebug ("Generating signature base for OAuth request.");
+
+			token = token ?? string.Empty;
+			tokenSecret = tokenSecret ?? string.Empty;
+
+			if (consumerKey == null) throw new ArgumentNullException ("consumerKey");
+
+			log.LogDebug ("URL: {0}", url.Query);
+
+			var signatureString = string.Empty;
+
+			switch (signatureType) {
+				case SignatureType.HMACSHA1:
+					signatureString = "HMAC-SHA1";
+					break;
+				case SignatureType.RSASHA1:
+					signatureString = "RSA-SHA1";
+					break;
+				case SignatureType.PLAINTEXT:
+					signatureString = SignatureType.PLAINTEXT.ToString ();
+					break;
+			}
+
+			parameters = GetQueryParameters (url.Query).Concat (new List<IQueryParameter<string>> {
+				new QueryParameter<string> (OAuthVersionKey, OAuthVersion, s => string.IsNullOrEmpty (s)),
+				new QueryParameter<string> (OAuthTimestampKey, ((long)timeStamp.TotalSeconds).ToString (), s => string.IsNullOrEmpty (s)),
+				new QueryParameter<string> (OAuthSignatureMethodKey, signatureString, s => string.IsNullOrEmpty (s)),
+				new QueryParameter<string> (OAuthNonceKey, nonce, s => string.IsNullOrEmpty (s)),
+				new QueryParameter<string> (OAuthConsumerKeyKey, consumerKey, s => string.IsNullOrEmpty (s))
+			}).ToList ();
+
+			if (!string.IsNullOrEmpty (token)) parameters.Add (new QueryParameter<string> (OAuthTokenKey, token, s => string.IsNullOrEmpty (s)));
+
+			log.LogDebug ("Normalizing URL for signature.");
+
+			normalizedUrl = string.Format ("{0}://{1}", url.Scheme, url.Host);
+			if (!((url.Scheme == "http" && url.Port == 80) || (url.Scheme == "https" && url.Port == 443))) normalizedUrl += ":" + url.Port;
+			normalizedUrl += url.AbsolutePath;
+
+			log.LogDebug ("Generated normalized URL: {0}", normalizedUrl);
+			log.LogDebug ("Normalizing request parameters.");
+
+			parameters.Sort ();
+			string normalizedRequestParameters = parameters.NormalizeRequestParameters ();
+
+			log.LogDebug ("Normalized request parameters {0}.", normalizedRequestParameters);
+			log.LogDebug ("Generating signature base from normalized URL and request parameters.");
+
+			var signatureBase = new StringBuilder ();
+			signatureBase.AppendFormat("{0}&", method.ToString ());
+			signatureBase.AppendFormat("{0}&", UrlEncode (normalizedUrl));
+			signatureBase.AppendFormat("{0}", UrlEncode (normalizedRequestParameters));
+
+			log.LogDebug ("Signature base: {0}", signatureBase.ToString ());
+
+			return signatureBase.ToString ();
+		}
+
+		/// <summary>
+		/// Generate the signature value based on the given signature base and hash algorithm.
+		/// </summary>
+		/// <param name="signatureBase">
+		/// 	The signature based as produced by the GenerateSignatureBase method or by any other means.
+		/// </param>
+		/// <param name="hash">
+		/// 	The hash algorithm used to perform the hashing. If the hashing algorithm requires
+		/// 	initialization or a key it should be set prior to calling this method.
+		/// </param>
+		/// <returns>A Base64 string of the hash value.</returns>
+		private string GenerateSignatureUsingHash (string signatureBase, HashAlgorithm hash)
+		{
+			log.LogDebug ("Generating hashed signature.");
+			return ComputeHash (hash, signatureBase);
+		}
+
+		/// <summary>
+		/// Generates a signature using the HMAC-SHA1 algorithm
+		/// </summary>
+		/// <param name="url">The full URL that needs to be signed including its non-OAuth URL parameters.</param>
+		/// <param name="consumerKey">The consumer key.</param>
+		/// <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="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,
+			out List<IQueryParameter<string>> parameters)
+		{
+			log.LogDebug ("Generating signature using HMAC-SHA1 algorithm.");
+			return GenerateSignature (url, consumerKey, consumerSecret, token, tokenSecret, method, timeStamp, nonce,
+				SignatureType.HMACSHA1, out normalizedUrl, out parameters);
+		}
+
+		/// <summary>
+		/// Generates a signature using the specified signature type.
+		/// </summary>
+		/// <param name="url">The full URL that needs to be signed including its non-OAuth URL parameters.</param>
+		/// <param name="consumerKey">The consumer key.</param>
+		/// <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="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,
+			out string normalizedUrl, out List<IQueryParameter<string>> parameters)
+		{
+			log.LogDebug ("Generating signature using signature type {0}", signatureType);
+
+			normalizedUrl = null;
+			parameters = null;
+
+			switch (signatureType)
+			{
+				case SignatureType.PLAINTEXT:
+					var signature = UrlEncode (string.Format ("{0}&{1}", consumerSecret, tokenSecret));
+					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,
+						timeStamp, nonce, SignatureType.HMACSHA1, out normalizedUrl, out parameters);
+
+					var hmacsha1 = new HMACSHA1 ();
+					hmacsha1.Key = Encoding.ASCII.GetBytes (string.Format ("{0}&{1}",
+						UrlEncode (consumerSecret),
+						string.IsNullOrEmpty (tokenSecret) ? "" : UrlEncode(tokenSecret)));
+
+					var hashedSignature = GenerateSignatureUsingHash (signatureBase, hmacsha1);
+
+					log.LogDebug ("HMAC-SHA1 encoded signature {0} of consumer secret and token secret.", hashedSignature);
+					return hashedSignature;
+				case SignatureType.RSASHA1:
+					throw new NotImplementedException ();
+				default:
+					throw new ArgumentException ("Unknown signature type", "signatureType");
+			}
+		}
+
+		/// <summary>
+		/// Generate the timestamp for the signature.
+		/// </summary>
+		/// <returns>A string timestamp.</returns>
+		protected TimeSpan GenerateTimeStamp ()
+		{
+			log.LogDebug ("Generating time stamp.");
+			// Default implementation of UNIX time of the current UTC time
+			return DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
+		}
+
+		/// <summary>
+		/// Generate a nonce.
+		/// </summary>
+		/// <returns>A random nonce string.</returns>
+		protected virtual string GenerateNonce()
+		{
+			log.LogDebug ("Generating nonce.");
+			// Just a simple implementation of a random number between 123400 and 9999999
+			return random.Next (123400, 9999999).ToString ();
+		}
+	}
+}
\ No newline at end of file
diff --git a/Tomboy/Addins/WebSyncService/OAuth/Enums.cs b/Tomboy/Addins/WebSyncService/OAuth/Enums.cs
new file mode 100644
index 0000000..dd689af
--- /dev/null
+++ b/Tomboy/Addins/WebSyncService/OAuth/Enums.cs
@@ -0,0 +1,49 @@
+//
+// Enums.cs
+//  
+// Author:
+//       Bojan Rajkovic <bojanr brandeis edu>
+// 
+// Copyright (c) 2009 Bojan Rajkovic
+// 
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+// 
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+namespace OAuth
+{
+	/// <summary>
+	/// Provides a predefined set of signature algorithms that are supported officially by the protocol
+	/// </summary>
+	public enum SignatureType
+	{
+		HMACSHA1,
+		PLAINTEXT,
+		RSASHA1
+	}
+
+	/// <summary>
+	/// The HTTP request method.
+	/// </summary>
+	public enum RequestMethod
+	{
+		GET,
+		POST,
+		PUT,
+		DELETE
+	}
+}
\ No newline at end of file
diff --git a/Tomboy/Addins/WebSyncService/OAuth/Extensions.cs b/Tomboy/Addins/WebSyncService/OAuth/Extensions.cs
new file mode 100644
index 0000000..1380e8c
--- /dev/null
+++ b/Tomboy/Addins/WebSyncService/OAuth/Extensions.cs
@@ -0,0 +1,76 @@
+//
+// Extensions.cs
+//  
+// Author:
+//       Bojan Rajkovic <bojanr brandeis edu>
+// 
+// Copyright (c) 2009 Bojan Rajkovic
+// 
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+// 
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using Mono.Rocks;
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+
+namespace OAuth
+{
+	/// <summary>
+	/// Extensions to help work with some BCL classes.
+	/// </summary>
+	public static class Extensions
+	{
+		/// <summary>
+		/// Normalizes the request parameters according to the query string specification.
+		/// </summary>
+		/// <param name="parameters">The list of parameters already sorted.</param>
+		/// <returns>A string representing the normalized parameters.</returns>
+		public static string NormalizeRequestParameters<T> (this IEnumerable<IQueryParameter<T>> parameters)
+		{
+			return parameters.Implode ("&");
+		}
+
+		/// <summary>
+		/// Normalizes the request parameters according to the query string specification.
+		/// </summary>
+		/// <param name="parameters">The list of parameters already sorted.</param>
+		/// <returns>A string representing the normalized parameters.</returns>
+		public static string NormalizeRequestParameters (this IEnumerable<IQueryParameter> parameters)
+		{
+			return parameters.Implode ("&");
+		}
+		
+		public static string NormalizeRequestParameters<T> (this IEnumerable<IQueryParameter<T>> parameters, Func<IQueryParameter<T>, string> selector)
+		{
+			return parameters.Implode ("&", selector);
+		}
+
+		/// <summary>
+		/// Converts a NameValueCollection to an *actual* <see cref="Dictionary{T, T}">Dictionary&lt;T, T&gt;</see>.
+		/// </summary>
+		/// <param name="self">The NameValueCollection to convert.</param>
+		/// <returns>A <see cref="Dictionary{T, T}">Dictionary&lt;T, T&gt;</see>.</returns>
+		public static IDictionary<string, string> ToDictionary (this NameValueCollection self)
+		{
+			SortedDictionary<string, string> dict = new SortedDictionary<string, string> ();
+			foreach (var key in self.AllKeys) dict[key] = self[key];
+			return dict;
+		}
+	}
+}
diff --git a/Tomboy/Addins/WebSyncService/OAuth/Mono.Rocks/Check.cs b/Tomboy/Addins/WebSyncService/OAuth/Mono.Rocks/Check.cs
new file mode 100644
index 0000000..5c8b8ac
--- /dev/null
+++ b/Tomboy/Addins/WebSyncService/OAuth/Mono.Rocks/Check.cs
@@ -0,0 +1,46 @@
+//
+// Check.cs
+//
+// Author:
+//   Jb Evain (jbevain novell com)
+//
+// Copyright (c) 2007 Novell, Inc. (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.Rocks {
+
+	static class Check {
+		public static void Self (object self)
+		{
+			if (self == null)
+				throw new ArgumentNullException ("self");
+		}
+
+		public static void Selector (object selector)
+		{
+			if (selector == null)
+				throw new ArgumentNullException ("selector");
+		}
+	}
+}
diff --git a/Tomboy/Addins/WebSyncService/OAuth/Mono.Rocks/IEnumerable.cs b/Tomboy/Addins/WebSyncService/OAuth/Mono.Rocks/IEnumerable.cs
new file mode 100644
index 0000000..34fdb6f
--- /dev/null
+++ b/Tomboy/Addins/WebSyncService/OAuth/Mono.Rocks/IEnumerable.cs
@@ -0,0 +1,66 @@
+//
+// IEnumerableRocks.cs
+//
+// Author:
+//   Jb Evain (jbevain novell com)
+//   Jonathan Pryor  <jpryor novell com>
+//
+// Copyright (c) 2007-2009 Novell, Inc. (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Mono.Rocks {
+
+	public static class IEnumerableRocks {
+
+		public static string Implode<TSource> (this IEnumerable<TSource> self, string separator)
+		{
+			return Implode (self, separator, e => e.ToString ());
+		}
+
+		public static string Implode<TSource> (this IEnumerable<TSource> self)
+		{
+			return Implode (self, null);
+		}
+
+		public static string Implode<TSource> (this IEnumerable<TSource> self, string separator, Func<TSource, string> selector)
+		{
+			Check.Self (self);
+			Check.Selector (selector);
+
+			var c = self as ICollection<TSource>;
+			string[] values = new string [c != null ? c.Count : 10];
+			int i = 0;
+			foreach (var e in self) {
+				if (values.Length == i)
+					Array.Resize (ref values, i*2);
+				values [i++] = selector (e);
+			}
+			if (i < values.Length)
+				Array.Resize (ref values, i);
+			return string.Join (separator, values);
+		}
+	}
+}
diff --git a/Tomboy/Addins/WebSyncService/OAuth/QueryParameter.cs b/Tomboy/Addins/WebSyncService/OAuth/QueryParameter.cs
new file mode 100644
index 0000000..f888959
--- /dev/null
+++ b/Tomboy/Addins/WebSyncService/OAuth/QueryParameter.cs
@@ -0,0 +1,204 @@
+//
+// QueryParameter.cs
+//  
+// Author:
+//       Bojan Rajkovic <bojanr brandeis edu>
+// 
+// Copyright (c) 2009 Bojan Rajkovic
+// 
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+// 
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+
+namespace OAuth
+{
+	/// <summary>
+	/// A common interface for query parameters to create lists.
+	/// </summary>
+	public interface IQueryParameter<T> : IComparable<IQueryParameter<T>>, IEquatable<IQueryParameter<T>>
+	{
+		string Name
+		{
+			get;
+		}
+
+		T Value
+		{
+			get;
+		}
+	}
+
+	public interface IQueryParameter
+	{
+		string Name
+		{
+			get;
+		}
+
+		object Value
+		{
+			get;
+		}
+	}
+
+	/// <summary>
+	/// Provides a structure to hold query parameters for easier query string building.
+	/// </summary>
+	public class QueryParameter<T> : IQueryParameter, IQueryParameter<T>, IEquatable<QueryParameter<T>>, IComparable<QueryParameter<T>>
+		where T : IComparable<T>, IEquatable<T>
+	{
+		private T _value;
+
+		/// <summary>
+		/// Creates an instance of the QueryParameter object.
+		/// </summary>
+		/// <param name="name">The parameter name.</param>
+		/// <param name="value">The parameter value.</param>
+		/// <param name="omitCondition">The condition under which to omit the parameter.</param>
+		public QueryParameter (string name, T value, Predicate<T> omitCondition)
+		{
+			if (string.IsNullOrEmpty (name)) throw new ArgumentNullException ("name");
+
+			Name = name;
+			_value = value;
+			Omit = omitCondition;
+		}
+
+		/// <summary>
+		/// The condition under which to omit the QueryParameter.
+		/// </summary>
+		public Predicate<T> Omit
+		{
+			get;
+			private set;
+		}
+
+		object IQueryParameter.Value
+		{
+			get { return _value; }
+		}
+
+		/// <summary>
+		/// The parameter name.
+		/// </summary>
+		public string Name
+		{
+			get;
+			private set;
+		}
+
+		/// <summary>
+		/// The parameter value.
+		/// </summary>
+		public T Value
+		{
+			get { return _value; }
+			private set { _value = value; }
+		}
+
+		/// <summary>
+		/// Creates a string that represents the query parameter (or omits it if the omit condition is met).
+		/// </summary>
+		/// <returns></returns>
+		public override string ToString ()
+		{
+			if (Omit (_value)) return null;
+			else return string.Format ("{0}={1}", Name, _value);
+		}
+
+		public bool Equals (IQueryParameter<T> other)
+		{
+			return Equals ((QueryParameter<T>) other);
+		}
+
+		public int CompareTo (IQueryParameter<T> other)
+		{
+			return CompareTo ((QueryParameter<T>) other);
+		}
+
+		/// <summary>
+		/// Check if this QueryParameter instance is equal to another.
+		/// </summary>
+		/// <param name="other">The other query parameter instance.</param>
+		/// <returns>True if the objects are equal, false otherwise.</returns>
+		public bool Equals (QueryParameter<T> other)
+		{
+			return string.Equals (other.Name, Name, StringComparison.OrdinalIgnoreCase)
+				? EqualityComparer<T>.Default.Equals (other.Value, _value)
+				: false;
+		}
+
+		/// <summary>
+		/// Compares this QueryParameter instance to another.
+		/// </summary>
+		/// <param name="other">The other QueryParameter.</param>
+		/// <returns>-1 if this instance is to be sorted after, 0 if they are equal, 1 if this one is to be sorted before.</returns>
+		public int CompareTo (QueryParameter<T> other)
+		{
+			var value = string.Compare (Name, other.Name, StringComparison.OrdinalIgnoreCase);
+			return value == 0 ? Comparer<T>.Default.Compare (_value, other.Value) : value;
+		}
+
+		/// <summary>
+		/// Compares this object to another for equality.
+		/// </summary>
+		/// <param name="obj">The other object.</param>
+		/// <returns>True if they are the same, false otherwise.</returns>
+		public override bool Equals (object obj)
+		{
+			if (obj == null)
+				throw new ArgumentNullException ("obj");
+			if (obj is QueryParameter<T>)
+				return Equals ((QueryParameter<T>) obj);
+			else throw new ArgumentException ("obj is not a QueryParameter<T>.", "obj");
+		}
+
+		/// <summary>
+		/// Gets the hash code for this QueryParameter instance.
+		/// </summary>
+		/// <returns>The hash code.</returns>
+		public override int GetHashCode ()
+		{
+			return Name.GetHashCode () ^ _value.GetHashCode ();
+		}
+
+		/// <summary>
+		/// Checks if two instances of <see cref="QueryParameter{T}">QueryParameter&lt;T&gt;</see> are equal.
+		/// </summary>
+		/// <param name="lhs">One instance.</param>
+		/// <param name="rhs">The other instance.</param>
+		/// <returns>True if they are same, false otherwise.</returns>
+		public static bool operator == (QueryParameter<T> lhs, QueryParameter<T> rhs)
+		{
+			return lhs.Equals (rhs);
+		}
+
+		/// <summary>
+		/// Checks if two instances of <see cref="QueryParameter{T}">QueryParameter&lt;T&gt;</see> are not equal.
+		/// </summary>
+		/// <param name="lhs">One instance.</param>
+		/// <param name="rhs">The other instance.</param>
+		/// <returns>True if they are different, false otherwise.</returns>
+		public static bool operator != (QueryParameter<T> lhs, QueryParameter<T> rhs)
+		{
+			return !(lhs == rhs);
+		}
+	}
+}
diff --git a/Tomboy/Addins/WebSyncService/WebSyncPreferencesWidget.cs b/Tomboy/Addins/WebSyncService/WebSyncPreferencesWidget.cs
new file mode 100644
index 0000000..d255a9d
--- /dev/null
+++ b/Tomboy/Addins/WebSyncService/WebSyncPreferencesWidget.cs
@@ -0,0 +1,169 @@
+// Permission is hereby granted, free of charge, to any person obtaining 
+// a copy of this software and associated documentation files (the 
+// "Software"), to deal in the Software without restriction, including 
+// without limitation the rights to use, copy, modify, merge, publish, 
+// distribute, sublicense, and/or sell copies of the Software, and to 
+// permit persons to whom the Software is furnished to do so, subject to 
+// the following conditions: 
+//  
+// The above copyright notice and this permission notice shall be 
+// included in all copies or substantial portions of the Software. 
+//  
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
+// 
+// Copyright (c) 2009 Novell, Inc. (http://www.novell.com) 
+// 
+// Authors: 
+//      Sandy Armstrong <sanfordarmstrong gmail com>
+// 
+
+using System;
+
+using Mono.Unix;
+
+namespace Tomboy.WebSync
+{
+	public class WebSyncPreferencesWidget : Gtk.VBox
+	{
+		private Gtk.Entry serverEntry;
+		private Gtk.Button authButton;
+		private bool authReqested;
+		private Api.OAuth oauth;
+		
+		public WebSyncPreferencesWidget (Api.OAuth oauth, string server) : base (false, 5)
+		{
+			this.oauth = oauth;
+			
+			Gtk.Table prefsTable = new Gtk.Table (1, 2, false);
+			prefsTable.RowSpacing = 5;
+			prefsTable.ColumnSpacing = 10;
+
+			serverEntry = new Gtk.Entry ();
+			serverEntry.Text = server;
+			AddRow (prefsTable, serverEntry, Catalog.GetString ("Se_rver:"), 0);
+			
+			Add (prefsTable);
+
+			authButton = new Gtk.Button ();
+			// TODO: If Auth is valid, this text should change
+			if (!Auth.IsAccessToken)
+				authButton.Label = Catalog.GetString ("_Connect to Server");
+			else {
+				authButton.Label = Catalog.GetString ("Connected");
+				authButton.Sensitive = false;
+			}
+			authButton.Clicked += OnAuthButtonClicked;
+
+			serverEntry.Changed += delegate {
+				Auth = null;
+			};
+
+			Add (authButton);
+
+			// TODO: Add a section that shows the user something to verify they put
+			//       in the right URL...something that constructs their user URL, maybe?
+			ShowAll ();
+		}
+
+		public string Server {
+			get {
+				return serverEntry.Text.Trim ();
+			}
+		}
+
+		public Api.OAuth Auth {
+			get { return oauth; }
+			set {
+				oauth = value;
+				if (oauth == null) {
+					authButton.Label =
+						Catalog.GetString ("Connect to Server");
+					authButton.Sensitive = true;
+				}
+			}
+		}
+
+		private void OnAuthButtonClicked (object sender, EventArgs args)
+		{
+			// TODO: Move this
+			if (Auth == null) {
+				Auth = new Api.OAuth ();
+				Auth.AuthorizeLocation = Server + "/oauth/authenticate/";
+				Auth.AccessTokenBaseUrl = Server + "/oauth/access_token/";
+				Auth.RequestTokenBaseUrl = Server + "/oauth/request_token/";
+				Auth.ConsumerKey = "abcdefg";
+				Auth.ConsumerSecret = "1234567";
+				Auth.Realm = "Snowy";
+			}
+
+			if (!Auth.IsAccessToken && !authReqested) {
+				string authUrl = string.Empty;
+				try {
+					authUrl = Auth.GetAuthorizationUrl ();
+				} catch (Exception e) {
+					Logger.Error ("Failed to get auth URL from " + Server + ". Exception was: " + e.ToString ());
+					authButton.Label = Catalog.GetString ("Server not responding. Try again later.");
+					return;
+				}
+				Logger.Debug ("Launching browser to authorize web sync: " + authUrl);
+				try {
+					Services.NativeApplication.OpenUrl (authUrl);
+					authReqested = true;
+					authButton.Label = Catalog.GetString ("Click Here After Authorizing");
+				} catch (Exception e) {
+					Logger.Error ("Exception opening URL: " + e.Message);
+					authButton.Label = Catalog.GetString ("Set the default browser and try again");
+				}
+			} 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");
+			}
+		}
+		
+		private void AddRow (Gtk.Table table, Gtk.Widget widget, string labelText, uint row)
+		{
+			Gtk.Label l = new Gtk.Label (labelText);
+			l.UseUnderline = true;
+			l.Xalign = 0.0f;
+			l.Show ();
+			table.Attach (l, 0, 1, row, row + 1,
+			              Gtk.AttachOptions.Fill,
+			              Gtk.AttachOptions.Expand | Gtk.AttachOptions.Fill,
+			              0, 0);
+
+			widget.Show ();
+			table.Attach (widget, 1, 2, row, row + 1,
+			              Gtk.AttachOptions.Expand | Gtk.AttachOptions.Fill,
+			              Gtk.AttachOptions.Expand | Gtk.AttachOptions.Fill,
+			              0, 0);
+
+			l.MnemonicWidget = widget;
+
+			// TODO: Tooltips
+		}
+	}
+}
diff --git a/Tomboy/Addins/WebSyncService/WebSyncServer.cs b/Tomboy/Addins/WebSyncService/WebSyncServer.cs
index 6f1485c..c181b62 100644
--- a/Tomboy/Addins/WebSyncService/WebSyncServer.cs
+++ b/Tomboy/Addins/WebSyncService/WebSyncServer.cs
@@ -35,27 +35,26 @@ namespace Tomboy.WebSync
 	public class WebSyncServer : SyncServer
 	{
 		private string serverUrl;
-		private string userName;
+		private IWebConnection connection;
 
-		private IAuthProvider auth;
-		
+		private RootInfo root;
 		private UserInfo user;
 		private List<NoteInfo> pendingCommits;
 		
-		public WebSyncServer (string serverUrl, string userName, string password)
+		public WebSyncServer (string serverUrl, IWebConnection connection)
 		{
 			this.serverUrl = serverUrl;
-			this.userName = userName;
-
-			auth = new BasicHttpAuthProvider (userName, password);
+			this.connection = connection;
 		}
 
 		#region SyncServer implementation
 		
 		public bool BeginSyncTransaction ()
 		{
-			// TODO: Check connection and auth (is getting user resource a sufficient check?)
-			user = UserInfo.GetUser (serverUrl, userName, auth);
+			// TODO: Check connection and auth (is getting root/user resources a sufficient check?)
+			string rootUri = serverUrl + "/api/1.0/";
+			root = RootInfo.GetRoot (rootUri, connection);
+			user = UserInfo.GetUser (root.User.ApiRef, connection);
 			if (user.LatestSyncRevision.HasValue)
 				LatestRevision = user.LatestSyncRevision.Value;
 			else
diff --git a/Tomboy/Addins/WebSyncService/WebSyncServiceAddin.cs b/Tomboy/Addins/WebSyncService/WebSyncServiceAddin.cs
index 12db110..3754cb5 100644
--- a/Tomboy/Addins/WebSyncService/WebSyncServiceAddin.cs
+++ b/Tomboy/Addins/WebSyncService/WebSyncServiceAddin.cs
@@ -35,18 +35,26 @@ namespace Tomboy.WebSync
 	public class WebSyncServiceAddin : SyncServiceAddin
 	{
 		private bool initialized;
+		private WebSyncPreferencesWidget prefsWidget;
 
 		private const string serverUrlPrefPath =
 			"/apps/tomboy/sync/tomboyweb/server";
-		private const string usernamePrefPath =
-			"/apps/tomboy/sync/tomboyweb/username";
-		// TODO: Migrate to keyring or hash it or something!
-		private const string passwordPrefPath =
-			"/apps/tomboy/sync/tomboyweb/password";
-
-		private Gtk.Entry serverEntry;
-		private Gtk.Entry userEntry;
-		private Gtk.Entry passwordEntry;
+		private const string accessTokenBaseUrlPrefPath =
+			"/apps/tomboy/sync/tomboyweb/oauth_access_token_base_url";
+		private const string authorizeLocationPrefPath =
+			"/apps/tomboy/sync/tomboyweb/oauth_authorize_location";
+		private const string consumerKeyPrefPath =
+			"/apps/tomboy/sync/tomboyweb/oauth_consumer_key";
+		private const string consumerSecretPrefPath =
+			"/apps/tomboy/sync/tomboyweb/oauth_consumer_secret";
+		private const string realmPrefPath =
+			"/apps/tomboy/sync/tomboyweb/oauth_realm";
+		private const string requestTokenBaseUrlPrefPath =
+			"/apps/tomboy/sync/tomboyweb/oauth_request_token_base_url";
+		private const string tokenPrefPath =
+			"/apps/tomboy/sync/tomboyweb/oauth_token";
+		private const string tokenSecretPrefPath =
+			"/apps/tomboy/sync/tomboyweb/oauth_token_secret";
 		
 		public WebSyncServiceAddin ()
 		{
@@ -66,11 +74,12 @@ namespace Tomboy.WebSync
 
 		public override bool IsConfigured {
 			get {
-				string serverPref, userPref, passPref;
-				GetConfigSettings (out serverPref, out userPref, out passPref);
+				string serverPref;
+				Api.OAuth oauth;
+				GetConfigSettings (out oauth, out serverPref);
 				return !string.IsNullOrEmpty (serverPref) &&
-					!string.IsNullOrEmpty (userPref) &&
-					!string.IsNullOrEmpty (passPref);
+					oauth != null &&
+					oauth.IsAccessToken;
 			}
 		}
 
@@ -82,40 +91,19 @@ namespace Tomboy.WebSync
 
 		public override Gtk.Widget CreatePreferencesControl ()
 		{
-			Gtk.Table prefsTable = new Gtk.Table (3, 2, false);
-			prefsTable.RowSpacing = 5;
-			prefsTable.ColumnSpacing = 10;
-
-			serverEntry = new Gtk.Entry ();
-			userEntry = new Gtk.Entry ();
-			passwordEntry = new Gtk.Entry ();
-
-			string serverPref, userPref, passPref;
-			GetConfigSettings (out serverPref, out userPref, out passPref);
-
-			serverEntry.Text = serverPref;
-			userEntry.Text = userPref;
-			passwordEntry.Text = passPref;
-			passwordEntry.Visibility = false;
-			
-			AddRow (prefsTable, serverEntry, Catalog.GetString ("Se_rver:"), 0);
-			AddRow (prefsTable, userEntry, Catalog.GetString ("User_name:"), 1);
-			AddRow (prefsTable, passwordEntry, Catalog.GetString ("_Password:"), 2);
-
-			prefsTable.Show ();
-
-			// TODO: Add a section that shows the user something to verify they put
-			//       in the right URL...something that constructs their user URL, maybe?
-			
-			return prefsTable;
+			string serverPref;
+			Api.OAuth oauth;
+			GetConfigSettings (out oauth, out serverPref);
+			prefsWidget = new WebSyncPreferencesWidget (oauth, serverPref);
+			return prefsWidget;
 		}
 
 		public override SyncServer CreateSyncServer ()
 		{
-			// TODO: What exactly do we need for connecting?
-			string serverPref, userPref, passPref;
-			GetConfigSettings (out serverPref, out userPref, out passPref);
-			return new WebSyncServer (serverPref, userPref, passPref);
+			string serverPref;
+			Api.OAuth oauth;
+			GetConfigSettings (out oauth, out serverPref);
+			return new WebSyncServer (serverPref, oauth);
 		}
 
 		public override void PostSyncCleanup ()
@@ -124,15 +112,18 @@ namespace Tomboy.WebSync
 
 		public override void ResetConfiguration ()
 		{
-			SaveConfigSettings (null, null, null);
+			SaveConfigSettings (null, null);
+			prefsWidget.Auth = null;
 		}
 
 		public override bool SaveConfiguration ()
 		{
-			string serverPref, userPref, passPref;
-			GetPrefWidgetSettings (out serverPref, out userPref, out passPref);
-			SaveConfigSettings (serverPref, userPref, passPref);
-			// TODO: Validate config
+			// TODO: Is this really sufficient validation?
+			//       Should we try a REST API request?
+			if (prefsWidget.Auth == null ||
+			    !prefsWidget.Auth.IsAccessToken)
+				return false;
+			SaveConfigSettings (prefsWidget.Auth, prefsWidget.Server);
 			return true;
 		}
 		
@@ -158,50 +149,77 @@ namespace Tomboy.WebSync
 
 		#region Private Members
 
-		private void GetPrefWidgetSettings (out string serverPref, out string userPref, out string passPref)
-		{
-			serverPref = serverEntry.Text.Trim ();
-			userPref = userEntry.Text.Trim ();
-			passPref = passwordEntry.Text.Trim ();
-		}
-
-		private void GetConfigSettings (out string serverPref, out string userPref, out string passPref)
+		private void GetConfigSettings (out Api.OAuth oauthConfig, out string serverPref)
 		{
 			serverPref = (string)
 				Preferences.Get (serverUrlPrefPath);
-			userPref = (string)
-				Preferences.Get (usernamePrefPath);
-			passPref = (string)
-				Preferences.Get (passwordPrefPath);
-		}
 
-		private void SaveConfigSettings (string serverPref, string userPref, string passPref)
+			oauthConfig = new Api.OAuth ();
+			oauthConfig.AccessTokenBaseUrl =
+				Preferences.Get (accessTokenBaseUrlPrefPath) as string;
+			oauthConfig.AuthorizeLocation =
+				Preferences.Get (authorizeLocationPrefPath) as string;
+			oauthConfig.ConsumerKey =
+				Preferences.Get (consumerKeyPrefPath) as string;
+			oauthConfig.ConsumerSecret =
+				Preferences.Get (consumerSecretPrefPath) as string;
+			oauthConfig.Realm =
+				Preferences.Get (realmPrefPath) as string;
+			oauthConfig.RequestTokenBaseUrl =
+				Preferences.Get (requestTokenBaseUrlPrefPath) as string;
+			oauthConfig.Token =
+				Preferences.Get (tokenPrefPath) as string;
+			oauthConfig.TokenSecret =
+				Preferences.Get (tokenSecretPrefPath) as string;
+
+			// The fact that the configuration was saved at all
+			// implies that the token is an access token.
+			// TODO: Any benefit in actually storing this bool, in
+			//       case of weird circumstances?
+			oauthConfig.IsAccessToken =
+				!String.IsNullOrEmpty (oauthConfig.Token);
+		}
+
+		private void SaveConfigSettings (Api.OAuth oauthConfig, string serverPref)
 		{
-			Preferences.Set (serverUrlPrefPath, serverPref);
-			Preferences.Set (usernamePrefPath, userPref);
-			Preferences.Set (passwordPrefPath, passPref);
-		}
-		
-		private void AddRow (Gtk.Table table, Gtk.Widget widget, string labelText, uint row)
+			Preferences.Set (serverUrlPrefPath, GetConfigString (serverPref));
+			Preferences.Set (accessTokenBaseUrlPrefPath,
+			                 oauthConfig != null ?
+			                 GetConfigString (oauthConfig.AccessTokenBaseUrl) :
+			                 String.Empty);
+			Preferences.Set (authorizeLocationPrefPath,
+			                 oauthConfig != null ?
+			                 GetConfigString (oauthConfig.AuthorizeLocation) :
+			                 String.Empty);
+			Preferences.Set (consumerKeyPrefPath,
+			                 oauthConfig != null ?
+			                 GetConfigString (oauthConfig.ConsumerKey) :
+			                 String.Empty);
+			Preferences.Set (consumerSecretPrefPath,
+			                 oauthConfig != null ?
+			                 GetConfigString (oauthConfig.ConsumerSecret) :
+			                 String.Empty);
+			Preferences.Set (realmPrefPath,
+			                 oauthConfig != null ?
+			                 GetConfigString (oauthConfig.Realm) :
+			                 String.Empty);
+			Preferences.Set (requestTokenBaseUrlPrefPath,
+			                 oauthConfig != null ?
+			                 GetConfigString (oauthConfig.RequestTokenBaseUrl) :
+			                 String.Empty);
+			Preferences.Set (tokenPrefPath,
+			                 oauthConfig != null ?
+			                 GetConfigString (oauthConfig.Token) :
+			                 String.Empty);
+			Preferences.Set (tokenSecretPrefPath,
+			                 oauthConfig != null ?
+			                 GetConfigString (oauthConfig.TokenSecret) :
+			                 String.Empty);
+		}
+
+		private string GetConfigString (string val)
 		{
-			Gtk.Label l = new Gtk.Label (labelText);
-			l.UseUnderline = true;
-			l.Xalign = 0.0f;
-			l.Show ();
-			table.Attach (l, 0, 1, row, row + 1,
-			              Gtk.AttachOptions.Fill,
-			              Gtk.AttachOptions.Expand | Gtk.AttachOptions.Fill,
-			              0, 0);
-
-			widget.Show ();
-			table.Attach (widget, 1, 2, row, row + 1,
-			              Gtk.AttachOptions.Expand | Gtk.AttachOptions.Fill,
-			              Gtk.AttachOptions.Expand | Gtk.AttachOptions.Fill,
-			              0, 0);
-
-			l.MnemonicWidget = widget;
-
-			// TODO: Tooltips
+			return val ?? String.Empty;
 		}
 
 		#endregion
diff --git a/po/POTFILES.in b/po/POTFILES.in
index a6d6568..889351e 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -40,6 +40,7 @@ Tomboy/Addins/Underline/UnderlineMenuItem.cs
 Tomboy/Addins/WebDavSyncService/WebDavSyncServiceAddin.cs
 Tomboy/Addins/WebDavSyncService/WebDavSyncService.addin.xml
 Tomboy/Addins/WebSyncService/WebSyncServiceAddin.cs
+Tomboy/Addins/WebSyncService/WebSyncPreferencesWidget.cs
 Tomboy/Addins/WebSyncService/WebSyncService.addin.xml
 Tomboy/Applet.cs
 Tomboy/MacApplication.cs



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]