a384eb2074fc72233be23b236171e73ec7810b9e
[pithos-ms-client] / trunk / Pithos.Client.WPF / PithosAccount.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.Network;
49 using log4net;
50
51 namespace Pithos.Client.WPF
52 {
53     using System;
54     using System.Collections.Generic;
55     using System.Linq;
56     using System.Text;
57     using System.Net;
58     using System.Threading.Tasks;
59     using System.Diagnostics.Contracts;
60     using System.Diagnostics;
61     using System.Net.Sockets;
62
63     /// <summary>
64     /// TODO: Update summary.
65     /// </summary>
66     /// <summary>
67     /// Retrieves an account name and token from PITHOS
68     /// </summary>
69     public static class PithosAccount
70     {
71         private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
72
73         /// <summary>
74         /// Asynchronously retrieves PITHOS credentials
75         /// </summary>
76         /// <param name="loginUrl">URL to retrieve the account info from PITHOS. Must end with =</param>
77         /// <returns>The credentials wrapped in a Task</returns>
78         public static Task<NetworkCredential> RetrieveCredentials(string loginUrl)
79         {
80             Contract.Requires(Uri.IsWellFormedUriString(loginUrl, UriKind.Absolute));
81
82             if (!Uri.IsWellFormedUriString(loginUrl, UriKind.Absolute))
83                 throw new ArgumentException("The pithosSite parameter must be a valid absolute URL", "loginUrl");
84             
85             int port = GetFreePort();
86             
87             //TODO:Force logout here to take care of existing cookies
88
89
90             var listenerUrl = String.Format("http://127.0.0.1:{0}/", port);
91
92             
93
94             var receiveCredentials = ListenForRedirectAsync(port);
95
96             var logoutUrl = loginUrl.Replace("login", "im/logout");
97             var logoutProcess=Process.Start(logoutUrl);            
98             TaskEx.Delay(2000).Wait();
99             var uriBuilder=new UriBuilder(loginUrl);                       
100             uriBuilder.Query="next=" + listenerUrl;
101
102             var retrieveUri = uriBuilder.Uri;
103             Log.InfoFormat("[RETRIEVE] Open Browser at {0}", retrieveUri);
104             Process.Start(retrieveUri.ToString());
105
106             return receiveCredentials;
107         }
108
109 /*
110         private static async Task<NetworkCredential> ListenHttpForRedirectAsync(string listenerUrl)
111         {
112             using (var listener = new HttpListener())
113             {
114                 listener.Prefixes.Add(listenerUrl);
115
116                 Log.InfoFormat("[RETRIEVE] Listening at {0}", listenerUrl);
117
118
119                 try
120                 {
121                     listener.Start();
122
123                     var context = await listener.GetContextAsync()
124                                             .WithTimeout(TimeSpan.FromMinutes(5));
125
126                     var request = context.Request;
127                     Log.InfoFormat("[RETRIEVE] Got Connection {0}", request.RawUrl);
128
129                     var query = request.QueryString;
130                     var userName = query["user"];
131                     var token = query["token"];
132                     if (String.IsNullOrWhiteSpace(userName) ||
133                         String.IsNullOrWhiteSpace(token))
134                     {
135                         Respond(context, "Failure", "The server did not return a username or token");
136                         Log.ErrorFormat("[RETRIEVE] No credentials returned by server");
137                         throw new Exception("The server did not return a username or token");
138                     }
139                     else
140                     {
141                         Log.InfoFormat("[RETRIEVE] Credentials retrieved user:{0} token:{1}", userName, token);
142                         Respond(context, "Authenticated", "Got It");
143
144                         return new NetworkCredential(userName, token);
145                     }
146                 }
147                 catch (Exception exc)
148                 {
149                     Log.Error("[RETRIEVE][ERROR] Receive connection {0}", exc);
150                     throw;
151                 }
152             }
153         }
154 */
155
156         private static async Task<NetworkCredential> ListenForRedirectAsync(int port)
157         {
158             var listener = new TcpListener(IPAddress.Any, port);            
159             Log.InfoFormat("[RETRIEVE] Listening at {0}", port);
160
161             try
162             {
163                 listener.Start();
164
165                 using (var client = await listener.AcceptTcpClientAsync()
166                                         .WithTimeout(TimeSpan.FromMinutes(5)))
167                 {
168                     using (var stream = client.GetStream())
169                     using (var reader=new StreamReader(stream))
170                     {
171                         var request = await reader.ReadLineAsync();
172                         //BUG
173                         //TODO: Add proper warnings here if the content is empty, don't just throw an exception
174                         //This may be a common occurence 
175
176                         if (String.IsNullOrWhiteSpace(request))
177                             throw new PithosException("The server did send any information");
178                         Log.InfoFormat("[RETRIEVE] Got Connection {0}", request);
179
180
181                         var items = ParseResponse(request);
182
183                         var userName = items["user"];
184                         var token = items["token"];
185                         if (String.IsNullOrWhiteSpace(userName) ||
186                             String.IsNullOrWhiteSpace(token))
187                         {
188                             Respond(stream, "Failure", "The server did not return a username or token");
189                             Log.ErrorFormat("[RETRIEVE] No credentials returned by server");
190                             throw new PithosException("The server did not return a username or token");
191                         }
192                         else
193                         {
194                             Log.InfoFormat("[RETRIEVE] Credentials retrieved user:{0} token:{1}", userName, token);
195                             Respond(stream, "Authenticated", "Got It");                            
196                         }
197                         return new NetworkCredential(userName, token);
198                     }
199                 }
200             }
201             catch (Exception exc)
202             {
203                 Log.Error("[RETRIEVE][ERROR] Receive connection {0}", exc);
204                 throw new PithosException("An error occured while retrieving credentials",exc);
205             }
206         }
207
208         private static Dictionary<string, string> ParseResponse(string request)
209         {
210             var parts = request.Split(' ');
211             var query = parts[1].TrimStart('/', '?');
212
213             var items = query.Split('&')
214                 .Select(pair => pair.Split('='))
215                 .ToDictionary(arr => arr[0].ToLower(), arr => Uri.UnescapeDataString(arr[1]));
216             return items;
217         }
218
219
220         private static void Respond(HttpListenerContext context,string title,string message)
221         {
222             var response = context.Response;
223             var html = String.Format("<html><head><title>{0}</title></head><body><h1>{1}</h1></body></html>", title, message);
224             var outBuffer = Encoding.UTF8.GetBytes(html);
225             response.ContentLength64 = outBuffer.Length;
226             using (var stream = response.OutputStream)
227             {
228                 stream.Write(outBuffer, 0, outBuffer.Length);
229             }
230
231             Log.InfoFormat("[RETRIEVE] Responded");
232         }
233
234         private static void Respond(Stream stream,string title,string message)
235         {
236             
237             var html = String.Format("<html><head><title>{0}</title></head><body><h1>{1}</h1></body></html>", title, message);
238             
239             var response = new StringBuilder();
240             response.AppendLine("HTTP/1.1 200 OK");
241             response.AppendFormat("Content-Length: {0}\n", html.Length);
242             response.AppendLine("Server: Microsoft-HTTPAPI/2.0");
243             response.AppendFormat("Date: {0}\n\n", DateTime.UtcNow);
244             response.AppendLine(html);
245
246             var outBuffer=Encoding.UTF8.GetBytes(response.ToString());
247             
248             stream.Write(outBuffer, 0, outBuffer.Length);
249
250             Log.InfoFormat("[RETRIEVE] Responded");
251         }
252
253        
254         /// <summary>
255         /// Locates a free local port 
256         /// </summary>
257         /// <returns>A free port</returns>
258         /// <remarks>The method starts and stops a TcpListener on port 0 to locate a free port.</remarks>
259         public static int GetFreePort()
260         {
261             //The TcpListener will locate a free port             
262             var listener = new TcpListener(IPAddress.Any, 0);
263             listener.Start();
264             var port = ((IPEndPoint)listener.LocalEndpoint).Port;
265             listener.Stop();
266             return port;
267         }
268     }
269 }