4549b119c59bf62f29024a3b25b7a7513c6eef76
[pithos-ms-client] / trunk%2FPithos.Client.WPF%2FPithosAccount.cs
1 #region
2 /* -----------------------------------------------------------------------
3  * <copyright file="PithosAccount.cs" company="GRNet">
4  * 
5  * Copyright 2011-2012 GRNET S.A. All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or
8  * without modification, are permitted provided that the following
9  * conditions are met:
10  *
11  *   1. Redistributions of source code must retain the above
12  *      copyright notice, this list of conditions and the following
13  *      disclaimer.
14  *
15  *   2. Redistributions in binary form must reproduce the above
16  *      copyright notice, this list of conditions and the following
17  *      disclaimer in the documentation and/or other materials
18  *      provided with the distribution.
19  *
20  *
21  * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
22  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
25  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
28  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  *
34  * The views and conclusions contained in the software and
35  * documentation are those of the authors and should not be
36  * interpreted as representing official policies, either expressed
37  * or implied, of GRNET S.A.
38  * </copyright>
39  * -----------------------------------------------------------------------
40  */
41 #endregion
42
43 // </copyright>
44 // -----------------------------------------------------------------------
45
46 using System.IO;
47 using System.Reflection;
48 using Pithos.Client.WPF.Preferences;
49 using Pithos.Network;
50 using log4net;
51
52 namespace Pithos.Client.WPF
53 {
54     using System;
55     using System.Collections.Generic;
56     using System.Linq;
57     using System.Text;
58     using System.Net;
59     using System.Threading.Tasks;
60     using System.Diagnostics.Contracts;
61     using System.Diagnostics;
62     using System.Net.Sockets;
63
64     /// <summary>
65     /// TODO: Update summary.
66     /// </summary>
67     /// <summary>
68     /// Retrieves an account name and token from PITHOS
69     /// </summary>
70     public static class PithosAccount
71     {
72         private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
73
74         /// <summary>
75         /// Asynchronously retrieves PITHOS credentials
76         /// </summary>
77         /// <param name="loginUrl">URL to retrieve the account info from PITHOS. Must end with =</param>
78         /// <returns>The credentials wrapped in a Task</returns>
79         public static NetworkCredential RetrieveCredentials(string loginUrl,string accountName=null)
80         {
81             Contract.Requires(Uri.IsWellFormedUriString(loginUrl, UriKind.Absolute));
82
83             if (!Uri.IsWellFormedUriString(loginUrl, UriKind.Absolute))
84                 throw new ArgumentException("The pithosSite parameter must be a valid absolute URL", "loginUrl");
85             
86             //int port = GetFreePort();
87             
88             //TODO:Force logout here to take care of existing cookies
89
90
91             //var listenerUrl = String.Format("http://127.0.0.1:{0}", port);
92             var listenerUrl = "pithos://127.0.0.1";
93
94             
95
96             //var receiveCredentials = ListenForRedirectAsync(port);
97
98             //var logoutUrl = loginUrl.Replace("login", "im/logout");
99             //var logoutProcess=Process.Start(logoutUrl);            
100             //TaskEx.Delay(2000).Wait();
101             var uriBuilder = new UriBuilder(loginUrl)
102                                  {
103                                      Query = String.Format("next={0}&force=", listenerUrl)
104                                  };
105
106             var retrieveUri = uriBuilder.Uri;
107
108
109             var browser = new LoginView(retrieveUri,accountName);            
110             if (true == browser.ShowDialog())
111             {
112                 return new NetworkCredential(browser.Account, browser.Token);
113             }
114             return null;
115
116 /*
117
118             Log.InfoFormat("[RETRIEVE] Open Browser at {0}", retrieveUri);
119             Process.Start(retrieveUri.ToString());
120
121             return receiveCredentials;
122 */
123         }
124
125 /*
126         private static async Task<NetworkCredential> ListenHttpForRedirectAsync(string listenerUrl)
127         {
128             using (var listener = new HttpListener())
129             {
130                 listener.Prefixes.Add(listenerUrl);
131
132                 Log.InfoFormat("[RETRIEVE] Listening at {0}", listenerUrl);
133
134
135                 try
136                 {
137                     listener.Start();
138
139                     var context = await listener.GetContextAsync()
140                                             .WithTimeout(TimeSpan.FromMinutes(5));
141
142                     var request = context.Request;
143                     Log.InfoFormat("[RETRIEVE] Got Connection {0}", request.RawUrl);
144
145                     var query = request.QueryString;
146                     var userName = query["user"];
147                     var token = query["token"];
148                     if (String.IsNullOrWhiteSpace(userName) ||
149                         String.IsNullOrWhiteSpace(token))
150                     {
151                         Respond(context, "Failure", "The server did not return a username or token");
152                         Log.ErrorFormat("[RETRIEVE] No credentials returned by server");
153                         throw new Exception("The server did not return a username or token");
154                     }
155                     else
156                     {
157                         Log.InfoFormat("[RETRIEVE] Credentials retrieved user:{0} token:{1}", userName, token);
158                         Respond(context, "Authenticated", "Got It");
159
160                         return new NetworkCredential(userName, token);
161                     }
162                 }
163                 catch (Exception exc)
164                 {
165                     Log.Error("[RETRIEVE][ERROR] Receive connection {0}", exc);
166                     throw;
167                 }
168             }
169         }
170 */
171
172         private static async Task<NetworkCredential> ListenForRedirectAsync(int port)
173         {
174             var listener = new TcpListener(IPAddress.Any, port);            
175             Log.InfoFormat("[RETRIEVE] Listening at {0}", port);
176
177             try
178             {
179                 listener.Start();
180
181                 using (var client = await listener.AcceptTcpClientAsync()
182                                         .WithTimeout(TimeSpan.FromMinutes(5)))
183                 {
184                     using (var stream = client.GetStream())
185                     using (var reader=new StreamReader(stream))
186                     {
187                         var request = await reader.ReadLineAsync();
188                         //BUG
189                         //TODO: Add proper warnings here if the content is empty, don't just throw an exception
190                         //This may be a common occurence 
191
192                         if (String.IsNullOrWhiteSpace(request))
193                             throw new PithosException("The server did send any information");
194                         Log.InfoFormat("[RETRIEVE] Got Connection {0}", request);
195
196
197                         var items = ParseResponse(request);
198
199                         var userName = items["user"];
200                         var token = items["token"];
201                         if (String.IsNullOrWhiteSpace(userName) ||
202                             String.IsNullOrWhiteSpace(token))
203                         {
204                             Respond(stream, "Failure", "The server did not return a username or token");
205                             Log.ErrorFormat("[RETRIEVE] No credentials returned by server");
206                             throw new PithosException("The server did not return a username or token");
207                         }
208                         else
209                         {
210                             Log.InfoFormat("[RETRIEVE] Credentials retrieved user:{0} token:{1}", userName, token);
211                             Respond(stream, "Authenticated", "Got It");                            
212                         }
213                         return new NetworkCredential(userName, token);
214                     }
215                 }
216             }
217             catch (Exception exc)
218             {
219                 Log.Error("[RETRIEVE][ERROR] Receive connection {0}", exc);
220                 throw new PithosException("An error occured while retrieving credentials",exc);
221             }
222         }
223
224         private static Dictionary<string, string> ParseResponse(string request)
225         {
226             var parts = request.Split(' ');
227             var query = parts[1].TrimStart('/', '?');
228
229             var items = query.Split('&')
230                 .Select(pair => pair.Split('='))
231                 .ToDictionary(arr => arr[0].ToLower(), arr => Uri.UnescapeDataString(arr[1]));
232             return items;
233         }
234
235
236         private static void Respond(HttpListenerContext context,string title,string message)
237         {
238             var response = context.Response;
239             var html = String.Format("<html><head><title>{0}</title></head><body><h1>{1}</h1></body></html>", title, message);
240             var outBuffer = Encoding.UTF8.GetBytes(html);
241             response.ContentLength64 = outBuffer.Length;
242             using (var stream = response.OutputStream)
243             {
244                 stream.Write(outBuffer, 0, outBuffer.Length);
245             }
246
247             Log.InfoFormat("[RETRIEVE] Responded");
248         }
249
250         private static void Respond(Stream stream,string title,string message)
251         {
252             
253             var html = String.Format("<html><head><title>{0}</title></head><body><h1>{1}</h1></body></html>", title, message);
254             
255             var response = new StringBuilder();
256             response.AppendLine("HTTP/1.1 200 OK");
257             response.AppendFormat("Content-Length: {0}\n", html.Length);
258             response.AppendLine("Server: Microsoft-HTTPAPI/2.0");
259             response.AppendFormat("Date: {0}\n\n", DateTime.UtcNow);
260             response.AppendLine(html);
261
262             var outBuffer=Encoding.UTF8.GetBytes(response.ToString());
263             
264             stream.Write(outBuffer, 0, outBuffer.Length);
265
266             Log.InfoFormat("[RETRIEVE] Responded");
267         }
268
269        
270         /// <summary>
271         /// Locates a free local port 
272         /// </summary>
273         /// <returns>A free port</returns>
274         /// <remarks>The method starts and stops a TcpListener on port 0 to locate a free port.</remarks>
275         public static int GetFreePort()
276         {
277             //The TcpListener will locate a free port             
278             var listener = new TcpListener(IPAddress.Any, 0);
279             listener.Start();
280             var port = ((IPEndPoint)listener.LocalEndpoint).Port;
281             listener.Stop();
282             return port;
283         }
284     }
285 }