New SQLite version
[pithos-ms-client] / trunk / Pithos.Client.WPF / PithosAccount.cs
index ad22534..4549b11 100644 (file)
@@ -1,9 +1,51 @@
-// -----------------------------------------------------------------------
-// <copyright file="PithosAccount.cs" company="Microsoft">
-// TODO: Update copyright text.
+#region
+/* -----------------------------------------------------------------------
+ * <copyright file="PithosAccount.cs" company="GRNet">
+ * 
+ * Copyright 2011-2012 GRNET S.A. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above
+ *      copyright notice, this list of conditions and the following
+ *      disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above
+ *      copyright notice, this list of conditions and the following
+ *      disclaimer in the documentation and/or other materials
+ *      provided with the distribution.
+ *
+ *
+ * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and
+ * documentation are those of the authors and should not be
+ * interpreted as representing official policies, either expressed
+ * or implied, of GRNET S.A.
+ * </copyright>
+ * -----------------------------------------------------------------------
+ */
+#endregion
+
 // </copyright>
 // -----------------------------------------------------------------------
 
+using System.IO;
+using System.Reflection;
+using Pithos.Client.WPF.Preferences;
 using Pithos.Network;
 using log4net;
 
@@ -27,83 +69,175 @@ namespace Pithos.Client.WPF
     /// </summary>
     public static class PithosAccount
     {
-        private static readonly ILog Log = LogManager.GetLogger(typeof(PithosAccount));
+        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
 
         /// <summary>
         /// Asynchronously retrieves PITHOS credentials
         /// </summary>
-        /// <param name="pithosSite">URL to retrieve the account info from PITHOS. Must end with =</param>
+        /// <param name="loginUrl">URL to retrieve the account info from PITHOS. Must end with =</param>
         /// <returns>The credentials wrapped in a Task</returns>
-        public static Task<NetworkCredential> RetrieveCredentialsAsync(string pithosSite)
+        public static NetworkCredential RetrieveCredentials(string loginUrl,string accountName=null)
         {
-            Contract.Requires(Uri.IsWellFormedUriString(pithosSite, UriKind.Absolute));
+            Contract.Requires(Uri.IsWellFormedUriString(loginUrl, UriKind.Absolute));
 
-            if (!Uri.IsWellFormedUriString(pithosSite, UriKind.Absolute))
-                throw new ArgumentException("The pithosSite parameter must be a valid absolute URL", "pithosSite");
+            if (!Uri.IsWellFormedUriString(loginUrl, UriKind.Absolute))
+                throw new ArgumentException("The pithosSite parameter must be a valid absolute URL", "loginUrl");
+            
+            //int port = GetFreePort();
             
-            int port = GetFreePort();
+            //TODO:Force logout here to take care of existing cookies
 
 
-            var listenerUrl = String.Format("http://127.0.0.1:{0}/", port);
+            //var listenerUrl = String.Format("http://127.0.0.1:{0}", port);
+            var listenerUrl = "pithos://127.0.0.1";
 
-            HttpListener listener = new HttpListener();
-            listener.Prefixes.Add(listenerUrl);
+            
 
-            Log.InfoFormat("[RETRIEVE] Listening at {0}", listenerUrl);
+            //var receiveCredentials = ListenForRedirectAsync(port);
 
-            listener.Start();
-            
-            var startListening = Task.Factory.FromAsync<HttpListenerContext>(listener.BeginGetContext, listener.EndGetContext, null)
-                .WithTimeout(TimeSpan.FromMinutes(5));
+            //var logoutUrl = loginUrl.Replace("login", "im/logout");
+            //var logoutProcess=Process.Start(logoutUrl);            
+            //TaskEx.Delay(2000).Wait();
+            var uriBuilder = new UriBuilder(loginUrl)
+                                 {
+                                     Query = String.Format("next={0}&force=", listenerUrl)
+                                 };
+
+            var retrieveUri = uriBuilder.Uri;
+
+
+            var browser = new LoginView(retrieveUri,accountName);            
+            if (true == browser.ShowDialog())
+            {
+                return new NetworkCredential(browser.Account, browser.Token);
+            }
+            return null;
+
+/*
+
+            Log.InfoFormat("[RETRIEVE] Open Browser at {0}", retrieveUri);
+            Process.Start(retrieveUri.ToString());
+
+            return receiveCredentials;
+*/
+        }
 
-            var receiveCredentials=startListening.ContinueWith(tc =>
+/*
+        private static async Task<NetworkCredential> ListenHttpForRedirectAsync(string listenerUrl)
+        {
+            using (var listener = new HttpListener())
+            {
+                listener.Prefixes.Add(listenerUrl);
+
+                Log.InfoFormat("[RETRIEVE] Listening at {0}", listenerUrl);
+
+
+                try
                 {
-                    try
+                    listener.Start();
+
+                    var context = await listener.GetContextAsync()
+                                            .WithTimeout(TimeSpan.FromMinutes(5));
+
+                    var request = context.Request;
+                    Log.InfoFormat("[RETRIEVE] Got Connection {0}", request.RawUrl);
+
+                    var query = request.QueryString;
+                    var userName = query["user"];
+                    var token = query["token"];
+                    if (String.IsNullOrWhiteSpace(userName) ||
+                        String.IsNullOrWhiteSpace(token))
+                    {
+                        Respond(context, "Failure", "The server did not return a username or token");
+                        Log.ErrorFormat("[RETRIEVE] No credentials returned by server");
+                        throw new Exception("The server did not return a username or token");
+                    }
+                    else
                     {
-                        if (tc.IsFaulted)
+                        Log.InfoFormat("[RETRIEVE] Credentials retrieved user:{0} token:{1}", userName, token);
+                        Respond(context, "Authenticated", "Got It");
+
+                        return new NetworkCredential(userName, token);
+                    }
+                }
+                catch (Exception exc)
+                {
+                    Log.Error("[RETRIEVE][ERROR] Receive connection {0}", exc);
+                    throw;
+                }
+            }
+        }
+*/
+
+        private static async Task<NetworkCredential> ListenForRedirectAsync(int port)
+        {
+            var listener = new TcpListener(IPAddress.Any, port);            
+            Log.InfoFormat("[RETRIEVE] Listening at {0}", port);
+
+            try
+            {
+                listener.Start();
+
+                using (var client = await listener.AcceptTcpClientAsync()
+                                        .WithTimeout(TimeSpan.FromMinutes(5)))
+                {
+                    using (var stream = client.GetStream())
+                    using (var reader=new StreamReader(stream))
+                    {
+                        var request = await reader.ReadLineAsync();
+                        //BUG
+                        //TODO: Add proper warnings here if the content is empty, don't just throw an exception
+                        //This may be a common occurence 
+
+                        if (String.IsNullOrWhiteSpace(request))
+                            throw new PithosException("The server did send any information");
+                        Log.InfoFormat("[RETRIEVE] Got Connection {0}", request);
+
+
+                        var items = ParseResponse(request);
+
+                        var userName = items["user"];
+                        var token = items["token"];
+                        if (String.IsNullOrWhiteSpace(userName) ||
+                            String.IsNullOrWhiteSpace(token))
                         {
-                            Log.Error("[RETRIEVE][ERROR] Receive connection {0}", tc.Exception);
-                            throw tc.Exception;
+                            Respond(stream, "Failure", "The server did not return a username or token");
+                            Log.ErrorFormat("[RETRIEVE] No credentials returned by server");
+                            throw new PithosException("The server did not return a username or token");
                         }
                         else
                         {
-
-                            var context = tc.Result;
-                            var request = context.Request;
-                            Log.InfoFormat("[RETRIEVE] Got Connection {0}", request.RawUrl);
-
-                            var query = request.QueryString;
-                            var userName = query["user"];
-                            var token = query["token"];
-
                             Log.InfoFormat("[RETRIEVE] Credentials retrieved user:{0} token:{1}", userName, token);
-                            Respond(context);
-                            
-                            return new NetworkCredential(userName, token);
+                            Respond(stream, "Authenticated", "Got It");                            
                         }
-
-                    }
-                    finally
-                    {
-                        listener.Close();
+                        return new NetworkCredential(userName, token);
                     }
-                });
-
-            var uriBuilder=new UriBuilder(pithosSite);
-            uriBuilder.Path="login";            
-            uriBuilder.Query="next=" + listenerUrl;
+                }
+            }
+            catch (Exception exc)
+            {
+                Log.Error("[RETRIEVE][ERROR] Receive connection {0}", exc);
+                throw new PithosException("An error occured while retrieving credentials",exc);
+            }
+        }
 
-            var retrieveUri = uriBuilder.Uri;
-            Log.InfoFormat("[RETRIEVE] Open Browser at {0}", retrieveUri);
-            Process.Start(retrieveUri.ToString());
+        private static Dictionary<string, string> ParseResponse(string request)
+        {
+            var parts = request.Split(' ');
+            var query = parts[1].TrimStart('/', '?');
 
-            return receiveCredentials;
+            var items = query.Split('&')
+                .Select(pair => pair.Split('='))
+                .ToDictionary(arr => arr[0].ToLower(), arr => Uri.UnescapeDataString(arr[1]));
+            return items;
         }
 
-        private static void Respond(HttpListenerContext context)
+
+        private static void Respond(HttpListenerContext context,string title,string message)
         {
             var response = context.Response;
-            var outBuffer = Encoding.UTF8.GetBytes("<html><head><title>Authenticated</title></head><body><h1>Got It</h1></body></html>");
+            var html = String.Format("<html><head><title>{0}</title></head><body><h1>{1}</h1></body></html>", title, message);
+            var outBuffer = Encoding.UTF8.GetBytes(html);
             response.ContentLength64 = outBuffer.Length;
             using (var stream = response.OutputStream)
             {
@@ -112,6 +246,27 @@ namespace Pithos.Client.WPF
 
             Log.InfoFormat("[RETRIEVE] Responded");
         }
+
+        private static void Respond(Stream stream,string title,string message)
+        {
+            
+            var html = String.Format("<html><head><title>{0}</title></head><body><h1>{1}</h1></body></html>", title, message);
+            
+            var response = new StringBuilder();
+            response.AppendLine("HTTP/1.1 200 OK");
+            response.AppendFormat("Content-Length: {0}\n", html.Length);
+            response.AppendLine("Server: Microsoft-HTTPAPI/2.0");
+            response.AppendFormat("Date: {0}\n\n", DateTime.UtcNow);
+            response.AppendLine(html);
+
+            var outBuffer=Encoding.UTF8.GetBytes(response.ToString());
+            
+            stream.Write(outBuffer, 0, outBuffer.Length);
+
+            Log.InfoFormat("[RETRIEVE] Responded");
+        }
+
+       
         /// <summary>
         /// Locates a free local port 
         /// </summary>